Refactor ES6 module structure/split up Util
This commit restructures many of the ES6 modules, splitting them up to actual export multiple functions instead of a single object. It also splits up Util into multiple sub-modules, to make it easier to maintain. Finally, localisation is renamed to localization.
This commit is contained in:
parent
6e744119f8
commit
6d6f0db0da
|
@ -7,3 +7,5 @@ utils/websockify
|
|||
/build
|
||||
/lib
|
||||
recordings
|
||||
*.swp
|
||||
*~
|
||||
|
|
17
LICENSE.txt
17
LICENSE.txt
|
@ -5,20 +5,9 @@ Public License 2.0). The noVNC core library is composed of the
|
|||
Javascript code necessary for full noVNC operation. This includes (but
|
||||
is not limited to):
|
||||
|
||||
core/base64.js
|
||||
core/des.js
|
||||
core/display.js
|
||||
core/input/devices.js
|
||||
core/input/keysym.js
|
||||
core/logo.js
|
||||
core/playback.js
|
||||
core/rfb.js
|
||||
app/ui.js
|
||||
core/util.js
|
||||
core/websock.js
|
||||
app/webutil.js
|
||||
core/input/xtscancodes.js
|
||||
core/inflator.js
|
||||
core/**/*.js
|
||||
app/*.js
|
||||
test/playback.js
|
||||
|
||||
The HTML, CSS, font and images files that included with the noVNC
|
||||
source distibution (or repository) are not considered part of the
|
||||
|
|
|
@ -10,31 +10,21 @@
|
|||
/*jslint bitwise: false, white: false, browser: true, devel: true */
|
||||
/*global Util, window, document */
|
||||
|
||||
import Util from "../core/util.js";
|
||||
|
||||
// Globals defined here
|
||||
var WebUtil = {};
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in WebUtil
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
import { init_logging as main_init_logging } from '../core/util/logging.js';
|
||||
|
||||
// init log level reading the logging HTTP param
|
||||
WebUtil.init_logging = function (level) {
|
||||
export function init_logging (level) {
|
||||
"use strict";
|
||||
if (typeof level !== "undefined") {
|
||||
Util._log_level = level;
|
||||
main_init_logging(level);
|
||||
} else {
|
||||
var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/);
|
||||
Util._log_level = (param || ['', Util._log_level])[1];
|
||||
main_init_logging(param || undefined);
|
||||
}
|
||||
Util.init_logging();
|
||||
};
|
||||
|
||||
// Read a query string variable
|
||||
WebUtil.getQueryVar = function (name, defVal) {
|
||||
export function getQueryVar (name, defVal) {
|
||||
"use strict";
|
||||
var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
|
||||
match = document.location.href.match(re);
|
||||
|
@ -47,7 +37,7 @@ WebUtil.getQueryVar = function (name, defVal) {
|
|||
};
|
||||
|
||||
// Read a hash fragment variable
|
||||
WebUtil.getHashVar = function (name, defVal) {
|
||||
export function getHashVar (name, defVal) {
|
||||
"use strict";
|
||||
var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
|
||||
match = document.location.hash.match(re);
|
||||
|
@ -61,11 +51,11 @@ WebUtil.getHashVar = function (name, defVal) {
|
|||
|
||||
// Read a variable from the fragment or the query string
|
||||
// Fragment takes precedence
|
||||
WebUtil.getConfigVar = function (name, defVal) {
|
||||
export function getConfigVar (name, defVal) {
|
||||
"use strict";
|
||||
var val = WebUtil.getHashVar(name);
|
||||
var val = getHashVar(name);
|
||||
if (val === null) {
|
||||
val = WebUtil.getQueryVar(name, defVal);
|
||||
val = getQueryVar(name, defVal);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
@ -75,7 +65,7 @@ WebUtil.getConfigVar = function (name, defVal) {
|
|||
*/
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.createCookie = function (name, value, days) {
|
||||
export function createCookie (name, value, days) {
|
||||
"use strict";
|
||||
var date, expires;
|
||||
if (days) {
|
||||
|
@ -95,7 +85,7 @@ WebUtil.createCookie = function (name, value, days) {
|
|||
document.cookie = name + "=" + value + expires + "; path=/" + secure;
|
||||
};
|
||||
|
||||
WebUtil.readCookie = function (name, defaultValue) {
|
||||
export function readCookie (name, defaultValue) {
|
||||
"use strict";
|
||||
var nameEQ = name + "=",
|
||||
ca = document.cookie.split(';');
|
||||
|
@ -108,22 +98,24 @@ WebUtil.readCookie = function (name, defaultValue) {
|
|||
return (typeof defaultValue !== 'undefined') ? defaultValue : null;
|
||||
};
|
||||
|
||||
WebUtil.eraseCookie = function (name) {
|
||||
export function eraseCookie (name) {
|
||||
"use strict";
|
||||
WebUtil.createCookie(name, "", -1);
|
||||
createCookie(name, "", -1);
|
||||
};
|
||||
|
||||
/*
|
||||
* Setting handling.
|
||||
*/
|
||||
|
||||
WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
|
||||
var settings = {};
|
||||
|
||||
export function initSettings (callback /*, ...callbackArgs */) {
|
||||
"use strict";
|
||||
var callbackArgs = Array.prototype.slice.call(arguments, 1);
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.get(function (cfg) {
|
||||
WebUtil.settings = cfg;
|
||||
console.log(WebUtil.settings);
|
||||
settings = cfg;
|
||||
console.log(settings);
|
||||
if (callback) {
|
||||
callback.apply(this, callbackArgs);
|
||||
}
|
||||
|
@ -137,24 +129,24 @@ WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
|
|||
};
|
||||
|
||||
// No days means only for this browser session
|
||||
WebUtil.writeSetting = function (name, value) {
|
||||
export function writeSetting (name, value) {
|
||||
"use strict";
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
//console.log("writeSetting:", name, value);
|
||||
if (WebUtil.settings[name] !== value) {
|
||||
WebUtil.settings[name] = value;
|
||||
window.chrome.storage.sync.set(WebUtil.settings);
|
||||
if (settings[name] !== value) {
|
||||
settings[name] = value;
|
||||
window.chrome.storage.sync.set(settings);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem(name, value);
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.readSetting = function (name, defaultValue) {
|
||||
export function readSetting (name, defaultValue) {
|
||||
"use strict";
|
||||
var value;
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
value = WebUtil.settings[name];
|
||||
value = settings[name];
|
||||
} else {
|
||||
value = localStorage.getItem(name);
|
||||
}
|
||||
|
@ -168,17 +160,17 @@ WebUtil.readSetting = function (name, defaultValue) {
|
|||
}
|
||||
};
|
||||
|
||||
WebUtil.eraseSetting = function (name) {
|
||||
export function eraseSetting (name) {
|
||||
"use strict";
|
||||
if (window.chrome && window.chrome.storage) {
|
||||
window.chrome.storage.sync.remove(name);
|
||||
delete WebUtil.settings[name];
|
||||
delete settings[name];
|
||||
} else {
|
||||
localStorage.removeItem(name);
|
||||
}
|
||||
};
|
||||
|
||||
WebUtil.injectParamIfMissing = function (path, param, value) {
|
||||
export function injectParamIfMissing (path, param, value) {
|
||||
// force pretend that we're dealing with a relative path
|
||||
// (assume that we wanted an extra if we pass one in)
|
||||
path = "/" + path;
|
||||
|
@ -212,7 +204,7 @@ WebUtil.injectParamIfMissing = function (path, param, value) {
|
|||
// IE11 support or polyfill promises and fetch in IE11.
|
||||
// resolve will receive an object on success, while reject
|
||||
// will receive either an event or an error on failure.
|
||||
WebUtil.fetchJSON = function (path, resolve, reject) {
|
||||
export function fetchJSON(path, resolve, reject) {
|
||||
// NB: IE11 doesn't support JSON as a responseType
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('GET', path);
|
||||
|
@ -240,6 +232,4 @@ WebUtil.fetchJSON = function (path, resolve, reject) {
|
|||
};
|
||||
|
||||
req.send();
|
||||
};
|
||||
|
||||
export default WebUtil;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
/*jslint white: false */
|
||||
/*global console */
|
||||
|
||||
var Base64 = {
|
||||
export default {
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
|
||||
base64Pad : '=',
|
||||
|
@ -15,7 +15,7 @@ var Base64 = {
|
|||
encode: function (data) {
|
||||
"use strict";
|
||||
var result = '';
|
||||
var toBase64Table = Base64.toBase64Table;
|
||||
var toBase64Table = this.toBase64Table;
|
||||
var length = data.length;
|
||||
var lengthpad = (length % 3);
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
|
@ -63,8 +63,8 @@ var Base64 = {
|
|||
decode: function (data, offset) {
|
||||
"use strict";
|
||||
offset = typeof(offset) !== 'undefined' ? offset : 0;
|
||||
var toBinaryTable = Base64.toBinaryTable;
|
||||
var base64Pad = Base64.base64Pad;
|
||||
var toBinaryTable = this.toBinaryTable;
|
||||
var base64Pad = this.base64Pad;
|
||||
var result, result_length;
|
||||
var leftbits = 0; // number of bits decoded, but yet to be appended
|
||||
var leftdata = 0; // bits decoded, but yet to be appended
|
||||
|
@ -111,5 +111,3 @@ var Base64 = {
|
|||
return result;
|
||||
}
|
||||
}; /* End of Base64 namespace */
|
||||
|
||||
export default Base64;
|
||||
|
|
1428
core/display.js
1428
core/display.js
File diff suppressed because it is too large
Load Diff
|
@ -36,4 +36,3 @@ export default function Inflate() {
|
|||
|
||||
inflateInit(this.strm, this.windowBits);
|
||||
};
|
||||
|
||||
|
|
|
@ -8,395 +8,387 @@
|
|||
/*jslint browser: true, white: false */
|
||||
/*global window, Util */
|
||||
|
||||
import Util from "../util.js";
|
||||
import KeyboardUtil from "./util.js";
|
||||
import * as Log from '../util/logging.js';
|
||||
import { isTouchDevice } from '../util/browsers.js'
|
||||
import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js';
|
||||
import { set_defaults, make_properties } from '../util/properties.js';
|
||||
import * as KeyboardUtil from "./util.js";
|
||||
|
||||
//
|
||||
// Keyboard event handler
|
||||
//
|
||||
|
||||
export var Keyboard;
|
||||
const Keyboard = function (defaults) {
|
||||
this._keyDownList = []; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
set_defaults(this, defaults, {
|
||||
'target': document,
|
||||
'focused': true
|
||||
});
|
||||
|
||||
//
|
||||
// Keyboard event handler
|
||||
//
|
||||
|
||||
Keyboard = function (defaults) {
|
||||
this._keyDownList = []; // List of depressed keys
|
||||
// (even if they are happy)
|
||||
|
||||
Util.set_defaults(this, defaults, {
|
||||
'target': document,
|
||||
'focused': true
|
||||
});
|
||||
|
||||
// create the keyboard handler
|
||||
this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
|
||||
KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
|
||||
KeyboardUtil.TrackKeyState(
|
||||
KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
|
||||
)
|
||||
// create the keyboard handler
|
||||
this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
|
||||
KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
|
||||
KeyboardUtil.TrackKeyState(
|
||||
KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
|
||||
)
|
||||
); /* jshint newcap: true */
|
||||
)
|
||||
); /* jshint newcap: true */
|
||||
|
||||
// keep these here so we can refer to them later
|
||||
this._eventHandlers = {
|
||||
'keyup': this._handleKeyUp.bind(this),
|
||||
'keydown': this._handleKeyDown.bind(this),
|
||||
'keypress': this._handleKeyPress.bind(this),
|
||||
'blur': this._allKeysUp.bind(this)
|
||||
};
|
||||
// keep these here so we can refer to them later
|
||||
this._eventHandlers = {
|
||||
'keyup': this._handleKeyUp.bind(this),
|
||||
'keydown': this._handleKeyDown.bind(this),
|
||||
'keypress': this._handleKeyPress.bind(this),
|
||||
'blur': this._allKeysUp.bind(this)
|
||||
};
|
||||
};
|
||||
|
||||
Keyboard.prototype = {
|
||||
// private methods
|
||||
Keyboard.prototype = {
|
||||
// private methods
|
||||
|
||||
_handleRfbEvent: function (e) {
|
||||
if (this._onKeyPress) {
|
||||
Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
|
||||
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
|
||||
this._onKeyPress(e);
|
||||
}
|
||||
},
|
||||
|
||||
setQEMUVNCKeyboardHandler: function () {
|
||||
this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
|
||||
KeyboardUtil.TrackQEMUKeyState(
|
||||
this._handleRfbEvent.bind(this)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
_handleKeyDown: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keydown(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
Util.stopEvent(e);
|
||||
} else {
|
||||
// Allow the event to bubble and become a keyPress event which
|
||||
// will have the character code translated
|
||||
}
|
||||
},
|
||||
|
||||
_handleKeyPress: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keypress(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
Util.stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
_handleKeyUp: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keyup(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
Util.stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
_allKeysUp: function () {
|
||||
Util.Debug(">> Keyboard.allKeysUp");
|
||||
this._handler.releaseAll();
|
||||
Util.Debug("<< Keyboard.allKeysUp");
|
||||
},
|
||||
|
||||
// Public methods
|
||||
|
||||
grab: function () {
|
||||
//Util.Debug(">> Keyboard.grab");
|
||||
var c = this._target;
|
||||
|
||||
c.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
c.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
c.addEventListener('keypress', this._eventHandlers.keypress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
//Util.Debug("<< Keyboard.grab");
|
||||
},
|
||||
|
||||
ungrab: function () {
|
||||
//Util.Debug(">> Keyboard.ungrab");
|
||||
var c = this._target;
|
||||
|
||||
c.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
c.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||
c.removeEventListener('keypress', this._eventHandlers.keypress);
|
||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Release (key up) all keys that are in a down state
|
||||
this._allKeysUp();
|
||||
|
||||
//Util.Debug(">> Keyboard.ungrab");
|
||||
},
|
||||
|
||||
sync: function (e) {
|
||||
this._handler.syncModifiers(e);
|
||||
_handleRfbEvent: function (e) {
|
||||
if (this._onKeyPress) {
|
||||
Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
|
||||
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
|
||||
this._onKeyPress(e);
|
||||
}
|
||||
},
|
||||
|
||||
setQEMUVNCKeyboardHandler: function () {
|
||||
this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
|
||||
KeyboardUtil.TrackQEMUKeyState(
|
||||
this._handleRfbEvent.bind(this)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
_handleKeyDown: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keydown(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
stopEvent(e);
|
||||
} else {
|
||||
// Allow the event to bubble and become a keyPress event which
|
||||
// will have the character code translated
|
||||
}
|
||||
},
|
||||
|
||||
_handleKeyPress: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keypress(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
_handleKeyUp: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._handler.keyup(e)) {
|
||||
// Suppress bubbling/default actions
|
||||
stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
_allKeysUp: function () {
|
||||
Log.Debug(">> Keyboard.allKeysUp");
|
||||
this._handler.releaseAll();
|
||||
Log.Debug("<< Keyboard.allKeysUp");
|
||||
},
|
||||
|
||||
// Public methods
|
||||
|
||||
grab: function () {
|
||||
//Log.Debug(">> Keyboard.grab");
|
||||
var c = this._target;
|
||||
|
||||
c.addEventListener('keydown', this._eventHandlers.keydown);
|
||||
c.addEventListener('keyup', this._eventHandlers.keyup);
|
||||
c.addEventListener('keypress', this._eventHandlers.keypress);
|
||||
|
||||
// Release (key up) if window loses focus
|
||||
window.addEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
//Log.Debug("<< Keyboard.grab");
|
||||
},
|
||||
|
||||
ungrab: function () {
|
||||
//Log.Debug(">> Keyboard.ungrab");
|
||||
var c = this._target;
|
||||
|
||||
c.removeEventListener('keydown', this._eventHandlers.keydown);
|
||||
c.removeEventListener('keyup', this._eventHandlers.keyup);
|
||||
c.removeEventListener('keypress', this._eventHandlers.keypress);
|
||||
window.removeEventListener('blur', this._eventHandlers.blur);
|
||||
|
||||
// Release (key up) all keys that are in a down state
|
||||
this._allKeysUp();
|
||||
|
||||
//Log.Debug(">> Keyboard.ungrab");
|
||||
},
|
||||
|
||||
sync: function (e) {
|
||||
this._handler.syncModifiers(e);
|
||||
}
|
||||
};
|
||||
|
||||
make_properties(Keyboard, [
|
||||
['target', 'wo', 'dom'], // DOM element that captures keyboard input
|
||||
['focused', 'rw', 'bool'], // Capture and send key events
|
||||
|
||||
['onKeyPress', 'rw', 'func'] // Handler for key press/release
|
||||
]);
|
||||
|
||||
const Mouse = function (defaults) {
|
||||
this._mouseCaptured = false;
|
||||
|
||||
this._doubleClickTimer = null;
|
||||
this._lastTouchPos = null;
|
||||
|
||||
// Configuration attributes
|
||||
set_defaults(this, defaults, {
|
||||
'target': document,
|
||||
'focused': true,
|
||||
'touchButton': 1
|
||||
});
|
||||
|
||||
this._eventHandlers = {
|
||||
'mousedown': this._handleMouseDown.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mousewheel': this._handleMouseWheel.bind(this),
|
||||
'mousedisable': this._handleMouseDisable.bind(this)
|
||||
};
|
||||
};
|
||||
|
||||
Util.make_properties(Keyboard, [
|
||||
['target', 'wo', 'dom'], // DOM element that captures keyboard input
|
||||
['focused', 'rw', 'bool'], // Capture and send key events
|
||||
Mouse.prototype = {
|
||||
// private methods
|
||||
_captureMouse: function () {
|
||||
// capturing the mouse ensures we get the mouseup event
|
||||
setCapture(this._target);
|
||||
|
||||
['onKeyPress', 'rw', 'func'] // Handler for key press/release
|
||||
]);
|
||||
})();
|
||||
// some browsers give us mouseup events regardless,
|
||||
// so if we never captured the mouse, we can disregard the event
|
||||
this._mouseCaptured = true;
|
||||
},
|
||||
|
||||
export var Mouse;
|
||||
|
||||
(function () {
|
||||
Mouse = function (defaults) {
|
||||
this._mouseCaptured = false;
|
||||
_releaseMouse: function () {
|
||||
releaseCapture();
|
||||
this._mouseCaptured = false;
|
||||
},
|
||||
|
||||
_resetDoubleClickTimer: function () {
|
||||
this._doubleClickTimer = null;
|
||||
this._lastTouchPos = null;
|
||||
},
|
||||
|
||||
// Configuration attributes
|
||||
Util.set_defaults(this, defaults, {
|
||||
'target': document,
|
||||
'focused': true,
|
||||
'touchButton': 1
|
||||
});
|
||||
_handleMouseButton: function (e, down) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
this._eventHandlers = {
|
||||
'mousedown': this._handleMouseDown.bind(this),
|
||||
'mouseup': this._handleMouseUp.bind(this),
|
||||
'mousemove': this._handleMouseMove.bind(this),
|
||||
'mousewheel': this._handleMouseWheel.bind(this),
|
||||
'mousedisable': this._handleMouseDisable.bind(this)
|
||||
};
|
||||
};
|
||||
|
||||
Mouse.prototype = {
|
||||
// private methods
|
||||
_captureMouse: function () {
|
||||
// capturing the mouse ensures we get the mouseup event
|
||||
Util.setCapture(this._target);
|
||||
|
||||
// some browsers give us mouseup events regardless,
|
||||
// so if we never captured the mouse, we can disregard the event
|
||||
this._mouseCaptured = true;
|
||||
},
|
||||
|
||||
_releaseMouse: function () {
|
||||
Util.releaseCapture();
|
||||
this._mouseCaptured = false;
|
||||
},
|
||||
|
||||
_resetDoubleClickTimer: function () {
|
||||
this._doubleClickTimer = null;
|
||||
},
|
||||
|
||||
_handleMouseButton: function (e, down) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
|
||||
var pos = this._getMousePosition(e);
|
||||
|
||||
var bmask;
|
||||
if (e.touches || e.changedTouches) {
|
||||
// Touch device
|
||||
|
||||
// When two touches occur within 500 ms of each other and are
|
||||
// close enough together a double click is triggered.
|
||||
if (down == 1) {
|
||||
if (this._doubleClickTimer === null) {
|
||||
this._lastTouchPos = pos;
|
||||
} else {
|
||||
clearTimeout(this._doubleClickTimer);
|
||||
|
||||
// When the distance between the two touches is small enough
|
||||
// force the position of the latter touch to the position of
|
||||
// the first.
|
||||
|
||||
var xs = this._lastTouchPos.x - pos.x;
|
||||
var ys = this._lastTouchPos.y - pos.y;
|
||||
var d = Math.sqrt((xs * xs) + (ys * ys));
|
||||
|
||||
// The goal is to trigger on a certain physical width, the
|
||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
||||
var threshold = 20 * (window.devicePixelRatio || 1);
|
||||
if (d < threshold) {
|
||||
pos = this._lastTouchPos;
|
||||
}
|
||||
}
|
||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
||||
}
|
||||
bmask = this._touchButton;
|
||||
// If bmask is set
|
||||
} else if (e.which) {
|
||||
/* everything except IE */
|
||||
bmask = 1 << e.button;
|
||||
} else {
|
||||
/* IE including 9 */
|
||||
bmask = (e.button & 0x1) + // Left
|
||||
(e.button & 0x2) * 2 + // Right
|
||||
(e.button & 0x4) / 2; // Middle
|
||||
}
|
||||
|
||||
if (this._onMouseButton) {
|
||||
Util.Debug("onMouseButton " + (down ? "down" : "up") +
|
||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
||||
this._onMouseButton(pos.x, pos.y, down, bmask);
|
||||
}
|
||||
Util.stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseDown: function (e) {
|
||||
this._captureMouse();
|
||||
this._handleMouseButton(e, 1);
|
||||
},
|
||||
|
||||
_handleMouseUp: function (e) {
|
||||
if (!this._mouseCaptured) { return; }
|
||||
|
||||
this._handleMouseButton(e, 0);
|
||||
this._releaseMouse();
|
||||
},
|
||||
|
||||
_handleMouseWheel: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
|
||||
var pos = this._getMousePosition(e);
|
||||
|
||||
if (this._onMouseButton) {
|
||||
if (e.deltaX < 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
|
||||
} else if (e.deltaX > 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
|
||||
}
|
||||
|
||||
if (e.deltaY < 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
|
||||
} else if (e.deltaY > 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
|
||||
}
|
||||
}
|
||||
|
||||
Util.stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseMove: function (e) {
|
||||
if (! this._focused) { return; }
|
||||
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
|
||||
var pos = this._getMousePosition(e);
|
||||
if (this._onMouseMove) {
|
||||
this._onMouseMove(pos.x, pos.y);
|
||||
}
|
||||
Util.stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseDisable: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
/*
|
||||
* Stop propagation if inside canvas area
|
||||
* Note: This is only needed for the 'click' event as it fails
|
||||
* to fire properly for the target element so we have
|
||||
* to listen on the document element instead.
|
||||
*/
|
||||
if (e.target == this._target) {
|
||||
//Util.Debug("mouse event disabled");
|
||||
Util.stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
// Return coordinates relative to target
|
||||
_getMousePosition: function(e) {
|
||||
e = Util.getPointerEvent(e);
|
||||
var bounds = this._target.getBoundingClientRect();
|
||||
var x, y;
|
||||
// Clip to target bounds
|
||||
if (e.clientX < bounds.left) {
|
||||
x = 0;
|
||||
} else if (e.clientX >= bounds.right) {
|
||||
x = bounds.width - 1;
|
||||
} else {
|
||||
x = e.clientX - bounds.left;
|
||||
}
|
||||
if (e.clientY < bounds.top) {
|
||||
y = 0;
|
||||
} else if (e.clientY >= bounds.bottom) {
|
||||
y = bounds.height - 1;
|
||||
} else {
|
||||
y = e.clientY - bounds.top;
|
||||
}
|
||||
return {x:x, y:y};
|
||||
},
|
||||
|
||||
|
||||
// Public methods
|
||||
grab: function () {
|
||||
var c = this._target;
|
||||
|
||||
if (Util.isTouchDevice) {
|
||||
c.addEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
window.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.addEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
window.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.addEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
/* preventDefault() on mousedown doesn't stop this event for some
|
||||
reason so we have to explicitly block it */
|
||||
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
},
|
||||
|
||||
ungrab: function () {
|
||||
var c = this._target;
|
||||
|
||||
if (Util.isTouchDevice) {
|
||||
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
window.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
window.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
};
|
||||
|
||||
Util.make_properties(Mouse, [
|
||||
['target', 'ro', 'dom'], // DOM element that captures mouse input
|
||||
['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
|
||||
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
|
||||
var pos = this._getMousePosition(e);
|
||||
|
||||
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
|
||||
['onMouseMove', 'rw', 'func'], // Handler for mouse movement
|
||||
['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
]);
|
||||
})();
|
||||
var bmask;
|
||||
if (e.touches || e.changedTouches) {
|
||||
// Touch device
|
||||
|
||||
// When two touches occur within 500 ms of each other and are
|
||||
// close enough together a double click is triggered.
|
||||
if (down == 1) {
|
||||
if (this._doubleClickTimer === null) {
|
||||
this._lastTouchPos = pos;
|
||||
} else {
|
||||
clearTimeout(this._doubleClickTimer);
|
||||
|
||||
// When the distance between the two touches is small enough
|
||||
// force the position of the latter touch to the position of
|
||||
// the first.
|
||||
|
||||
var xs = this._lastTouchPos.x - pos.x;
|
||||
var ys = this._lastTouchPos.y - pos.y;
|
||||
var d = Math.sqrt((xs * xs) + (ys * ys));
|
||||
|
||||
// The goal is to trigger on a certain physical width, the
|
||||
// devicePixelRatio brings us a bit closer but is not optimal.
|
||||
var threshold = 20 * (window.devicePixelRatio || 1);
|
||||
if (d < threshold) {
|
||||
pos = this._lastTouchPos;
|
||||
}
|
||||
}
|
||||
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
|
||||
}
|
||||
bmask = this._touchButton;
|
||||
// If bmask is set
|
||||
} else if (e.which) {
|
||||
/* everything except IE */
|
||||
bmask = 1 << e.button;
|
||||
} else {
|
||||
/* IE including 9 */
|
||||
bmask = (e.button & 0x1) + // Left
|
||||
(e.button & 0x2) * 2 + // Right
|
||||
(e.button & 0x4) / 2; // Middle
|
||||
}
|
||||
|
||||
if (this._onMouseButton) {
|
||||
Log.Debug("onMouseButton " + (down ? "down" : "up") +
|
||||
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
|
||||
this._onMouseButton(pos.x, pos.y, down, bmask);
|
||||
}
|
||||
stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseDown: function (e) {
|
||||
this._captureMouse();
|
||||
this._handleMouseButton(e, 1);
|
||||
},
|
||||
|
||||
_handleMouseUp: function (e) {
|
||||
if (!this._mouseCaptured) { return; }
|
||||
|
||||
this._handleMouseButton(e, 0);
|
||||
this._releaseMouse();
|
||||
},
|
||||
|
||||
_handleMouseWheel: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
|
||||
var pos = this._getMousePosition(e);
|
||||
|
||||
if (this._onMouseButton) {
|
||||
if (e.deltaX < 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 5);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 5);
|
||||
} else if (e.deltaX > 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 6);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 6);
|
||||
}
|
||||
|
||||
if (e.deltaY < 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 3);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 3);
|
||||
} else if (e.deltaY > 0) {
|
||||
this._onMouseButton(pos.x, pos.y, 1, 1 << 4);
|
||||
this._onMouseButton(pos.x, pos.y, 0, 1 << 4);
|
||||
}
|
||||
}
|
||||
|
||||
stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseMove: function (e) {
|
||||
if (! this._focused) { return; }
|
||||
|
||||
if (this._notify) {
|
||||
this._notify(e);
|
||||
}
|
||||
|
||||
var pos = this._getMousePosition(e);
|
||||
if (this._onMouseMove) {
|
||||
this._onMouseMove(pos.x, pos.y);
|
||||
}
|
||||
stopEvent(e);
|
||||
},
|
||||
|
||||
_handleMouseDisable: function (e) {
|
||||
if (!this._focused) { return; }
|
||||
|
||||
/*
|
||||
* Stop propagation if inside canvas area
|
||||
* Note: This is only needed for the 'click' event as it fails
|
||||
* to fire properly for the target element so we have
|
||||
* to listen on the document element instead.
|
||||
*/
|
||||
if (e.target == this._target) {
|
||||
stopEvent(e);
|
||||
}
|
||||
},
|
||||
|
||||
// Return coordinates relative to target
|
||||
_getMousePosition: function(e) {
|
||||
e = getPointerEvent(e);
|
||||
var bounds = this._target.getBoundingClientRect();
|
||||
var x, y;
|
||||
// Clip to target bounds
|
||||
if (e.clientX < bounds.left) {
|
||||
x = 0;
|
||||
} else if (e.clientX >= bounds.right) {
|
||||
x = bounds.width - 1;
|
||||
} else {
|
||||
x = e.clientX - bounds.left;
|
||||
}
|
||||
if (e.clientY < bounds.top) {
|
||||
y = 0;
|
||||
} else if (e.clientY >= bounds.bottom) {
|
||||
y = bounds.height - 1;
|
||||
} else {
|
||||
y = e.clientY - bounds.top;
|
||||
}
|
||||
return {x:x, y:y};
|
||||
},
|
||||
|
||||
// Public methods
|
||||
grab: function () {
|
||||
var c = this._target;
|
||||
|
||||
if (isTouchDevice) {
|
||||
c.addEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
window.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.addEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.addEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
window.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.addEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.addEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
/* Prevent middle-click pasting (see above for why we bind to document) */
|
||||
document.addEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
/* preventDefault() on mousedown doesn't stop this event for some
|
||||
reason so we have to explicitly block it */
|
||||
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
},
|
||||
|
||||
ungrab: function () {
|
||||
var c = this._target;
|
||||
|
||||
if (isTouchDevice) {
|
||||
c.removeEventListener('touchstart', this._eventHandlers.mousedown);
|
||||
window.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchend', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('touchmove', this._eventHandlers.mousemove);
|
||||
}
|
||||
c.removeEventListener('mousedown', this._eventHandlers.mousedown);
|
||||
window.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mouseup', this._eventHandlers.mouseup);
|
||||
c.removeEventListener('mousemove', this._eventHandlers.mousemove);
|
||||
c.removeEventListener('wheel', this._eventHandlers.mousewheel);
|
||||
|
||||
document.removeEventListener('click', this._eventHandlers.mousedisable);
|
||||
|
||||
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
|
||||
}
|
||||
};
|
||||
|
||||
make_properties(Mouse, [
|
||||
['target', 'ro', 'dom'], // DOM element that captures mouse input
|
||||
['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
|
||||
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
|
||||
|
||||
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
|
||||
['onMouseMove', 'rw', 'func'], // Handler for mouse movement
|
||||
['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
|
||||
]);
|
||||
|
||||
export { Keyboard, Mouse };
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var KeyTable = {
|
||||
export default {
|
||||
XK_VoidSymbol: 0xffffff, /* Void symbol */
|
||||
|
||||
XK_BackSpace: 0xff08, /* Back space, back char */
|
||||
|
@ -378,5 +378,3 @@ var KeyTable = {
|
|||
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
|
||||
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
|
||||
};
|
||||
|
||||
export default KeyTable;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,293 +1,277 @@
|
|||
import KeyTable from "./keysym.js";
|
||||
import keysyms from "./keysymdef.js";
|
||||
|
||||
export function substituteCodepoint(cp) {
|
||||
// Any Unicode code points which do not have corresponding keysym entries
|
||||
// can be swapped out for another code point by adding them to this table
|
||||
var substitutions = {
|
||||
// {S,s} with comma below -> {S,s} with cedilla
|
||||
0x218 : 0x15e,
|
||||
0x219 : 0x15f,
|
||||
// {T,t} with comma below -> {T,t} with cedilla
|
||||
0x21a : 0x162,
|
||||
0x21b : 0x163
|
||||
};
|
||||
|
||||
var KeyboardUtil = {};
|
||||
var sub = substitutions[cp];
|
||||
return sub ? sub : cp;
|
||||
}
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
function isWindows() {
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
}
|
||||
function isLinux() {
|
||||
return navigator && !!(/linux/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
function substituteCodepoint(cp) {
|
||||
// Any Unicode code points which do not have corresponding keysym entries
|
||||
// can be swapped out for another code point by adding them to this table
|
||||
var substitutions = {
|
||||
// {S,s} with comma below -> {S,s} with cedilla
|
||||
0x218 : 0x15e,
|
||||
0x219 : 0x15f,
|
||||
// {T,t} with comma below -> {T,t} with cedilla
|
||||
0x21a : 0x162,
|
||||
0x21b : 0x163
|
||||
};
|
||||
|
||||
var sub = substitutions[cp];
|
||||
return sub ? sub : cp;
|
||||
}
|
||||
|
||||
function isMac() {
|
||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||
}
|
||||
function isWindows() {
|
||||
return navigator && !!(/win/i).exec(navigator.platform);
|
||||
}
|
||||
function isLinux() {
|
||||
return navigator && !!(/linux/i).exec(navigator.platform);
|
||||
}
|
||||
|
||||
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
|
||||
function hasShortcutModifier(charModifier, currentModifiers) {
|
||||
var mods = {};
|
||||
for (var key in currentModifiers) {
|
||||
if (parseInt(key) !== KeyTable.XK_Shift_L) {
|
||||
mods[key] = currentModifiers[key];
|
||||
}
|
||||
// Return true if a modifier which is not the specified char modifier (and is not shift) is down
|
||||
export function hasShortcutModifier(charModifier, currentModifiers) {
|
||||
var mods = {};
|
||||
for (var key in currentModifiers) {
|
||||
if (parseInt(key) !== KeyTable.XK_Shift_L) {
|
||||
mods[key] = currentModifiers[key];
|
||||
}
|
||||
}
|
||||
|
||||
var sum = 0;
|
||||
for (var k in currentModifiers) {
|
||||
if (mods[k]) {
|
||||
++sum;
|
||||
}
|
||||
var sum = 0;
|
||||
for (var k in currentModifiers) {
|
||||
if (mods[k]) {
|
||||
++sum;
|
||||
}
|
||||
if (hasCharModifier(charModifier, mods)) {
|
||||
return sum > charModifier.length;
|
||||
}
|
||||
if (hasCharModifier(charModifier, mods)) {
|
||||
return sum > charModifier.length;
|
||||
}
|
||||
else {
|
||||
return sum > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if the specified char modifier is currently down
|
||||
export function hasCharModifier(charModifier, currentModifiers) {
|
||||
if (charModifier.length === 0) { return false; }
|
||||
|
||||
for (var i = 0; i < charModifier.length; ++i) {
|
||||
if (!currentModifiers[charModifier[i]]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Helper object tracking modifier key state
|
||||
// and generates fake key events to compensate if it gets out of sync
|
||||
export function ModifierSync(charModifier) {
|
||||
if (!charModifier) {
|
||||
if (isMac()) {
|
||||
// on Mac, Option (AKA Alt) is used as a char modifier
|
||||
charModifier = [KeyTable.XK_Alt_L];
|
||||
}
|
||||
else if (isWindows()) {
|
||||
// on Windows, Ctrl+Alt is used as a char modifier
|
||||
charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L];
|
||||
}
|
||||
else if (isLinux()) {
|
||||
// on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
|
||||
charModifier = [KeyTable.XK_ISO_Level3_Shift];
|
||||
}
|
||||
else {
|
||||
return sum > 0;
|
||||
charModifier = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if the specified char modifier is currently down
|
||||
function hasCharModifier(charModifier, currentModifiers) {
|
||||
if (charModifier.length === 0) { return false; }
|
||||
var state = {};
|
||||
state[KeyTable.XK_Control_L] = false;
|
||||
state[KeyTable.XK_Alt_L] = false;
|
||||
state[KeyTable.XK_ISO_Level3_Shift] = false;
|
||||
state[KeyTable.XK_Shift_L] = false;
|
||||
state[KeyTable.XK_Meta_L] = false;
|
||||
|
||||
for (var i = 0; i < charModifier.length; ++i) {
|
||||
if (!currentModifiers[charModifier[i]]) {
|
||||
return false;
|
||||
}
|
||||
function sync(evt, keysym) {
|
||||
var result = [];
|
||||
function syncKey(keysym) {
|
||||
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
|
||||
}
|
||||
return true;
|
||||
|
||||
if (evt.ctrlKey !== undefined &&
|
||||
evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) {
|
||||
state[KeyTable.XK_Control_L] = evt.ctrlKey;
|
||||
result.push(syncKey(KeyTable.XK_Control_L));
|
||||
}
|
||||
if (evt.altKey !== undefined &&
|
||||
evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) {
|
||||
state[KeyTable.XK_Alt_L] = evt.altKey;
|
||||
result.push(syncKey(KeyTable.XK_Alt_L));
|
||||
}
|
||||
if (evt.altGraphKey !== undefined &&
|
||||
evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) {
|
||||
state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey;
|
||||
result.push(syncKey(KeyTable.XK_ISO_Level3_Shift));
|
||||
}
|
||||
if (evt.shiftKey !== undefined &&
|
||||
evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) {
|
||||
state[KeyTable.XK_Shift_L] = evt.shiftKey;
|
||||
result.push(syncKey(KeyTable.XK_Shift_L));
|
||||
}
|
||||
if (evt.metaKey !== undefined &&
|
||||
evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) {
|
||||
state[KeyTable.XK_Meta_L] = evt.metaKey;
|
||||
result.push(syncKey(KeyTable.XK_Meta_L));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function syncKeyEvent(evt, down) {
|
||||
var obj = getKeysym(evt);
|
||||
var keysym = obj ? obj.keysym : null;
|
||||
|
||||
// first, apply the event itself, if relevant
|
||||
if (keysym !== null && state[keysym] !== undefined) {
|
||||
state[keysym] = down;
|
||||
}
|
||||
return sync(evt, keysym);
|
||||
}
|
||||
|
||||
// Helper object tracking modifier key state
|
||||
// and generates fake key events to compensate if it gets out of sync
|
||||
function ModifierSync(charModifier) {
|
||||
if (!charModifier) {
|
||||
if (isMac()) {
|
||||
// on Mac, Option (AKA Alt) is used as a char modifier
|
||||
charModifier = [KeyTable.XK_Alt_L];
|
||||
}
|
||||
else if (isWindows()) {
|
||||
// on Windows, Ctrl+Alt is used as a char modifier
|
||||
charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L];
|
||||
}
|
||||
else if (isLinux()) {
|
||||
// on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
|
||||
charModifier = [KeyTable.XK_ISO_Level3_Shift];
|
||||
}
|
||||
else {
|
||||
charModifier = [];
|
||||
}
|
||||
}
|
||||
return {
|
||||
// sync on the appropriate keyboard event
|
||||
keydown: function(evt) { return syncKeyEvent(evt, true);},
|
||||
keyup: function(evt) { return syncKeyEvent(evt, false);},
|
||||
// Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
|
||||
syncAny: function(evt) { return sync(evt);},
|
||||
|
||||
var state = {};
|
||||
state[KeyTable.XK_Control_L] = false;
|
||||
state[KeyTable.XK_Alt_L] = false;
|
||||
state[KeyTable.XK_ISO_Level3_Shift] = false;
|
||||
state[KeyTable.XK_Shift_L] = false;
|
||||
state[KeyTable.XK_Meta_L] = false;
|
||||
// is a shortcut modifier down?
|
||||
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
|
||||
// if a char modifier is down, return the keys it consists of, otherwise return null
|
||||
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
|
||||
};
|
||||
}
|
||||
|
||||
function sync(evt, keysym) {
|
||||
var result = [];
|
||||
function syncKey(keysym) {
|
||||
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
|
||||
}
|
||||
|
||||
if (evt.ctrlKey !== undefined &&
|
||||
evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) {
|
||||
state[KeyTable.XK_Control_L] = evt.ctrlKey;
|
||||
result.push(syncKey(KeyTable.XK_Control_L));
|
||||
}
|
||||
if (evt.altKey !== undefined &&
|
||||
evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) {
|
||||
state[KeyTable.XK_Alt_L] = evt.altKey;
|
||||
result.push(syncKey(KeyTable.XK_Alt_L));
|
||||
}
|
||||
if (evt.altGraphKey !== undefined &&
|
||||
evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) {
|
||||
state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey;
|
||||
result.push(syncKey(KeyTable.XK_ISO_Level3_Shift));
|
||||
}
|
||||
if (evt.shiftKey !== undefined &&
|
||||
evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) {
|
||||
state[KeyTable.XK_Shift_L] = evt.shiftKey;
|
||||
result.push(syncKey(KeyTable.XK_Shift_L));
|
||||
}
|
||||
if (evt.metaKey !== undefined &&
|
||||
evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) {
|
||||
state[KeyTable.XK_Meta_L] = evt.metaKey;
|
||||
result.push(syncKey(KeyTable.XK_Meta_L));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function syncKeyEvent(evt, down) {
|
||||
var obj = getKeysym(evt);
|
||||
var keysym = obj ? obj.keysym : null;
|
||||
|
||||
// first, apply the event itself, if relevant
|
||||
if (keysym !== null && state[keysym] !== undefined) {
|
||||
state[keysym] = down;
|
||||
}
|
||||
return sync(evt, keysym);
|
||||
}
|
||||
|
||||
return {
|
||||
// sync on the appropriate keyboard event
|
||||
keydown: function(evt) { return syncKeyEvent(evt, true);},
|
||||
keyup: function(evt) { return syncKeyEvent(evt, false);},
|
||||
// Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
|
||||
syncAny: function(evt) { return sync(evt);},
|
||||
|
||||
// is a shortcut modifier down?
|
||||
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
|
||||
// if a char modifier is down, return the keys it consists of, otherwise return null
|
||||
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
|
||||
};
|
||||
// Get a key ID from a keyboard event
|
||||
// May be a string or an integer depending on the available properties
|
||||
export function getKey(evt){
|
||||
if ('keyCode' in evt && 'key' in evt) {
|
||||
return evt.key + ':' + evt.keyCode;
|
||||
}
|
||||
|
||||
// Get a key ID from a keyboard event
|
||||
// May be a string or an integer depending on the available properties
|
||||
function getKey(evt){
|
||||
if ('keyCode' in evt && 'key' in evt) {
|
||||
return evt.key + ':' + evt.keyCode;
|
||||
}
|
||||
else if ('keyCode' in evt) {
|
||||
return evt.keyCode;
|
||||
}
|
||||
else {
|
||||
return evt.key;
|
||||
}
|
||||
else if ('keyCode' in evt) {
|
||||
return evt.keyCode;
|
||||
}
|
||||
else {
|
||||
return evt.key;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the most reliable keysym value we can get from a key event
|
||||
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
|
||||
function getKeysym(evt){
|
||||
var codepoint;
|
||||
if (evt.char && evt.char.length === 1) {
|
||||
codepoint = evt.char.charCodeAt();
|
||||
}
|
||||
else if (evt.charCode) {
|
||||
codepoint = evt.charCode;
|
||||
}
|
||||
else if (evt.keyCode && evt.type === 'keypress') {
|
||||
// IE10 stores the char code as keyCode, and has no other useful properties
|
||||
codepoint = evt.keyCode;
|
||||
}
|
||||
if (codepoint) {
|
||||
return keysyms.fromUnicode(substituteCodepoint(codepoint));
|
||||
}
|
||||
// we could check evt.key here.
|
||||
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
|
||||
// so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
|
||||
// so we don't *need* it yet
|
||||
if (evt.keyCode) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
|
||||
}
|
||||
if (evt.which) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
|
||||
}
|
||||
// Get the most reliable keysym value we can get from a key event
|
||||
// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
|
||||
export function getKeysym(evt){
|
||||
var codepoint;
|
||||
if (evt.char && evt.char.length === 1) {
|
||||
codepoint = evt.char.charCodeAt();
|
||||
}
|
||||
else if (evt.charCode) {
|
||||
codepoint = evt.charCode;
|
||||
}
|
||||
else if (evt.keyCode && evt.type === 'keypress') {
|
||||
// IE10 stores the char code as keyCode, and has no other useful properties
|
||||
codepoint = evt.keyCode;
|
||||
}
|
||||
if (codepoint) {
|
||||
return keysyms.fromUnicode(substituteCodepoint(codepoint));
|
||||
}
|
||||
// we could check evt.key here.
|
||||
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
|
||||
// so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
|
||||
// so we don't *need* it yet
|
||||
if (evt.keyCode) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
|
||||
}
|
||||
if (evt.which) {
|
||||
return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Given a keycode, try to predict which keysym it might be.
|
||||
// If the keycode is unknown, null is returned.
|
||||
export function keysymFromKeyCode(keycode, shiftPressed) {
|
||||
if (typeof(keycode) !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Given a keycode, try to predict which keysym it might be.
|
||||
// If the keycode is unknown, null is returned.
|
||||
function keysymFromKeyCode(keycode, shiftPressed) {
|
||||
if (typeof(keycode) !== 'number') {
|
||||
return null;
|
||||
}
|
||||
// won't be accurate for azerty
|
||||
if (keycode >= 0x30 && keycode <= 0x39) {
|
||||
return keycode; // digit
|
||||
}
|
||||
if (keycode >= 0x41 && keycode <= 0x5a) {
|
||||
// remap to lowercase unless shift is down
|
||||
return shiftPressed ? keycode : keycode + 32; // A-Z
|
||||
}
|
||||
if (keycode >= 0x60 && keycode <= 0x69) {
|
||||
return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
|
||||
}
|
||||
|
||||
switch(keycode) {
|
||||
case 0x20: return KeyTable.XK_space;
|
||||
case 0x6a: return KeyTable.XK_KP_Multiply;
|
||||
case 0x6b: return KeyTable.XK_KP_Add;
|
||||
case 0x6c: return KeyTable.XK_KP_Separator;
|
||||
case 0x6d: return KeyTable.XK_KP_Subtract;
|
||||
case 0x6e: return KeyTable.XK_KP_Decimal;
|
||||
case 0x6f: return KeyTable.XK_KP_Divide;
|
||||
case 0xbb: return KeyTable.XK_plus;
|
||||
case 0xbc: return KeyTable.XK_comma;
|
||||
case 0xbd: return KeyTable.XK_minus;
|
||||
case 0xbe: return KeyTable.XK_period;
|
||||
}
|
||||
|
||||
return nonCharacterKey({keyCode: keycode});
|
||||
// won't be accurate for azerty
|
||||
if (keycode >= 0x30 && keycode <= 0x39) {
|
||||
return keycode; // digit
|
||||
}
|
||||
if (keycode >= 0x41 && keycode <= 0x5a) {
|
||||
// remap to lowercase unless shift is down
|
||||
return shiftPressed ? keycode : keycode + 32; // A-Z
|
||||
}
|
||||
if (keycode >= 0x60 && keycode <= 0x69) {
|
||||
return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
|
||||
}
|
||||
|
||||
// if the key is a known non-character key (any key which doesn't generate character data)
|
||||
// return its keysym value. Otherwise return null
|
||||
function nonCharacterKey(evt) {
|
||||
// evt.key not implemented yet
|
||||
if (!evt.keyCode) { return null; }
|
||||
var keycode = evt.keyCode;
|
||||
|
||||
if (keycode >= 0x70 && keycode <= 0x87) {
|
||||
return KeyTable.XK_F1 + keycode - 0x70; // F1-F24
|
||||
}
|
||||
switch (keycode) {
|
||||
|
||||
case 8 : return KeyTable.XK_BackSpace;
|
||||
case 13 : return KeyTable.XK_Return;
|
||||
|
||||
case 9 : return KeyTable.XK_Tab;
|
||||
|
||||
case 27 : return KeyTable.XK_Escape;
|
||||
case 46 : return KeyTable.XK_Delete;
|
||||
|
||||
case 36 : return KeyTable.XK_Home;
|
||||
case 35 : return KeyTable.XK_End;
|
||||
case 33 : return KeyTable.XK_Page_Up;
|
||||
case 34 : return KeyTable.XK_Page_Down;
|
||||
case 45 : return KeyTable.XK_Insert;
|
||||
|
||||
case 37 : return KeyTable.XK_Left;
|
||||
case 38 : return KeyTable.XK_Up;
|
||||
case 39 : return KeyTable.XK_Right;
|
||||
case 40 : return KeyTable.XK_Down;
|
||||
|
||||
case 16 : return KeyTable.XK_Shift_L;
|
||||
case 17 : return KeyTable.XK_Control_L;
|
||||
case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac
|
||||
|
||||
case 224 : return KeyTable.XK_Meta_L;
|
||||
case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr
|
||||
case 91 : return KeyTable.XK_Super_L; // also: Windows-key
|
||||
case 92 : return KeyTable.XK_Super_R; // also: Windows-key
|
||||
case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac
|
||||
default: return null;
|
||||
}
|
||||
switch(keycode) {
|
||||
case 0x20: return KeyTable.XK_space;
|
||||
case 0x6a: return KeyTable.XK_KP_Multiply;
|
||||
case 0x6b: return KeyTable.XK_KP_Add;
|
||||
case 0x6c: return KeyTable.XK_KP_Separator;
|
||||
case 0x6d: return KeyTable.XK_KP_Subtract;
|
||||
case 0x6e: return KeyTable.XK_KP_Decimal;
|
||||
case 0x6f: return KeyTable.XK_KP_Divide;
|
||||
case 0xbb: return KeyTable.XK_plus;
|
||||
case 0xbc: return KeyTable.XK_comma;
|
||||
case 0xbd: return KeyTable.XK_minus;
|
||||
case 0xbe: return KeyTable.XK_period;
|
||||
}
|
||||
|
||||
KeyboardUtil.hasShortcutModifier = hasShortcutModifier;
|
||||
KeyboardUtil.hasCharModifier = hasCharModifier;
|
||||
KeyboardUtil.ModifierSync = ModifierSync;
|
||||
KeyboardUtil.getKey = getKey;
|
||||
KeyboardUtil.getKeysym = getKeysym;
|
||||
KeyboardUtil.keysymFromKeyCode = keysymFromKeyCode;
|
||||
KeyboardUtil.nonCharacterKey = nonCharacterKey;
|
||||
KeyboardUtil.substituteCodepoint = substituteCodepoint;
|
||||
})();
|
||||
return nonCharacterKey({keyCode: keycode});
|
||||
}
|
||||
|
||||
KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
|
||||
// if the key is a known non-character key (any key which doesn't generate character data)
|
||||
// return its keysym value. Otherwise return null
|
||||
export function nonCharacterKey(evt) {
|
||||
// evt.key not implemented yet
|
||||
if (!evt.keyCode) { return null; }
|
||||
var keycode = evt.keyCode;
|
||||
|
||||
if (keycode >= 0x70 && keycode <= 0x87) {
|
||||
return KeyTable.XK_F1 + keycode - 0x70; // F1-F24
|
||||
}
|
||||
switch (keycode) {
|
||||
|
||||
case 8 : return KeyTable.XK_BackSpace;
|
||||
case 13 : return KeyTable.XK_Return;
|
||||
|
||||
case 9 : return KeyTable.XK_Tab;
|
||||
|
||||
case 27 : return KeyTable.XK_Escape;
|
||||
case 46 : return KeyTable.XK_Delete;
|
||||
|
||||
case 36 : return KeyTable.XK_Home;
|
||||
case 35 : return KeyTable.XK_End;
|
||||
case 33 : return KeyTable.XK_Page_Up;
|
||||
case 34 : return KeyTable.XK_Page_Down;
|
||||
case 45 : return KeyTable.XK_Insert;
|
||||
|
||||
case 37 : return KeyTable.XK_Left;
|
||||
case 38 : return KeyTable.XK_Up;
|
||||
case 39 : return KeyTable.XK_Right;
|
||||
case 40 : return KeyTable.XK_Down;
|
||||
|
||||
case 16 : return KeyTable.XK_Shift_L;
|
||||
case 17 : return KeyTable.XK_Control_L;
|
||||
case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac
|
||||
|
||||
case 224 : return KeyTable.XK_Meta_L;
|
||||
case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr
|
||||
case 91 : return KeyTable.XK_Super_L; // also: Windows-key
|
||||
case 92 : return KeyTable.XK_Super_R; // also: Windows-key
|
||||
case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function QEMUKeyEventDecoder (modifierState, next) {
|
||||
"use strict";
|
||||
|
||||
function sendAll(evts) {
|
||||
|
@ -333,7 +317,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
|
|||
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
|
||||
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
|
||||
|
||||
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt));
|
||||
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
|
||||
|
||||
next(result);
|
||||
return suppress;
|
||||
|
@ -357,7 +341,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
|
|||
};
|
||||
};
|
||||
|
||||
KeyboardUtil.TrackQEMUKeyState = function(next) {
|
||||
export function TrackQEMUKeyState (next) {
|
||||
"use strict";
|
||||
var state = [];
|
||||
|
||||
|
@ -425,7 +409,7 @@ KeyboardUtil.TrackQEMUKeyState = function(next) {
|
|||
// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
|
||||
// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
|
||||
// This information is collected into an object which is passed to the next() function. (one call per event)
|
||||
KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
|
||||
export function KeyEventDecoder (modifierState, next) {
|
||||
"use strict";
|
||||
function sendAll(evts) {
|
||||
for (var i = 0; i < evts.length; ++i) {
|
||||
|
@ -434,18 +418,18 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
|
|||
}
|
||||
function process(evt, type) {
|
||||
var result = {type: type};
|
||||
var keyId = KeyboardUtil.getKey(evt);
|
||||
var keyId = getKey(evt);
|
||||
if (keyId) {
|
||||
result.keyId = keyId;
|
||||
}
|
||||
|
||||
var keysym = KeyboardUtil.getKeysym(evt);
|
||||
var keysym = getKeysym(evt);
|
||||
|
||||
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
|
||||
// Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
|
||||
// "special" keys like enter, tab or backspace don't send keypress events,
|
||||
// and some browsers don't send keypresses at all if a modifier is down
|
||||
if (keysym && (type !== 'keydown' || KeyboardUtil.nonCharacterKey(evt) || hasModifier)) {
|
||||
if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) {
|
||||
result.keysym = keysym;
|
||||
}
|
||||
|
||||
|
@ -454,11 +438,11 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
|
|||
// Should we prevent the browser from handling the event?
|
||||
// Doing so on a keydown (in most browsers) prevents keypress from being generated
|
||||
// so only do that if we have to.
|
||||
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!KeyboardUtil.nonCharacterKey(evt));
|
||||
var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
|
||||
|
||||
// If a char modifier is down on a keydown, we need to insert a stall,
|
||||
// so VerifyCharModifier knows to wait and see if a keypress is comnig
|
||||
var stall = type === 'keydown' && modifierState.activeCharModifier() && !KeyboardUtil.nonCharacterKey(evt);
|
||||
var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt);
|
||||
|
||||
// if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
|
||||
var active = modifierState.activeCharModifier();
|
||||
|
@ -512,7 +496,7 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
|
|||
// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
|
||||
// The only way we can distinguish these cases is to wait and see if a keypress event arrives
|
||||
// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
|
||||
KeyboardUtil.VerifyCharModifier = function(next) {
|
||||
export function VerifyCharModifier (next) {
|
||||
"use strict";
|
||||
var queue = [];
|
||||
var timer = null;
|
||||
|
@ -569,7 +553,7 @@ KeyboardUtil.VerifyCharModifier = function(next) {
|
|||
// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
|
||||
// key repeat events should be merged into a single entry.
|
||||
// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
|
||||
KeyboardUtil.TrackKeyState = function(next) {
|
||||
export function TrackKeyState (next) {
|
||||
"use strict";
|
||||
var state = [];
|
||||
|
||||
|
@ -653,7 +637,7 @@ KeyboardUtil.TrackKeyState = function(next) {
|
|||
|
||||
// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
|
||||
// then the modifier must be "undone" before sending the @, and "redone" afterwards.
|
||||
KeyboardUtil.EscapeModifiers = function(next) {
|
||||
export function EscapeModifiers (next) {
|
||||
"use strict";
|
||||
return function(evt) {
|
||||
if (evt.type !== 'keydown' || evt.escape === undefined) {
|
||||
|
@ -674,5 +658,3 @@ KeyboardUtil.EscapeModifiers = function(next) {
|
|||
/* jshint shadow: false */
|
||||
};
|
||||
};
|
||||
|
||||
export default KeyboardUtil;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
var XtScancode = {
|
||||
export default {
|
||||
"Escape": 0x0001,
|
||||
"Digit1": 0x0002,
|
||||
"Digit2": 0x0003,
|
||||
|
@ -147,5 +147,3 @@ var XtScancode = {
|
|||
"LaunchMail": 0xE06C,
|
||||
"MediaSelect": 0xE06D,
|
||||
};
|
||||
|
||||
export default XtScancode
|
||||
|
|
4109
core/rfb.js
4109
core/rfb.js
File diff suppressed because it is too large
Load Diff
624
core/util.js
624
core/util.js
|
@ -1,624 +0,0 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/* jshint white: false, nonstandard: true */
|
||||
/*global window, console, document, navigator, ActiveXObject, INCLUDE_URI */
|
||||
|
||||
var Util = {};
|
||||
|
||||
/*
|
||||
* ------------------------------------------------------
|
||||
* Namespaced in Util
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Logging/debug routines
|
||||
*/
|
||||
|
||||
Util._log_level = 'warn';
|
||||
Util.init_logging = function (level) {
|
||||
"use strict";
|
||||
if (typeof level === 'undefined') {
|
||||
level = Util._log_level;
|
||||
} else {
|
||||
Util._log_level = level;
|
||||
}
|
||||
|
||||
Util.Debug = Util.Info = Util.Warn = Util.Error = function (msg) {};
|
||||
if (typeof window.console !== "undefined") {
|
||||
/* jshint -W086 */
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
Util.Debug = console.debug.bind(window.console);
|
||||
case 'info':
|
||||
Util.Info = console.info.bind(window.console);
|
||||
case 'warn':
|
||||
Util.Warn = console.warn.bind(window.console);
|
||||
case 'error':
|
||||
Util.Error = console.error.bind(window.console);
|
||||
case 'none':
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid logging type '" + level + "'");
|
||||
}
|
||||
/* jshint +W086 */
|
||||
}
|
||||
};
|
||||
Util.get_logging = function () {
|
||||
return Util._log_level;
|
||||
};
|
||||
// Initialize logging level
|
||||
Util.init_logging();
|
||||
|
||||
Util.make_property = function (proto, name, mode, type) {
|
||||
"use strict";
|
||||
|
||||
var getter;
|
||||
if (type === 'arr') {
|
||||
getter = function (idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
return this['_' + name][idx];
|
||||
} else {
|
||||
return this['_' + name];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
getter = function () {
|
||||
return this['_' + name];
|
||||
};
|
||||
}
|
||||
|
||||
var make_setter = function (process_val) {
|
||||
if (process_val) {
|
||||
return function (val, idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
this['_' + name][idx] = process_val(val);
|
||||
} else {
|
||||
this['_' + name] = process_val(val);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function (val, idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
this['_' + name][idx] = val;
|
||||
} else {
|
||||
this['_' + name] = val;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var setter;
|
||||
if (type === 'bool') {
|
||||
setter = make_setter(function (val) {
|
||||
if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else if (type === 'int') {
|
||||
setter = make_setter(function (val) { return parseInt(val, 10); });
|
||||
} else if (type === 'float') {
|
||||
setter = make_setter(parseFloat);
|
||||
} else if (type === 'str') {
|
||||
setter = make_setter(String);
|
||||
} else if (type === 'func') {
|
||||
setter = make_setter(function (val) {
|
||||
if (!val) {
|
||||
return function () {};
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
} else if (type === 'arr' || type === 'dom' || type == 'raw') {
|
||||
setter = make_setter();
|
||||
} else {
|
||||
throw new Error('Unknown property type ' + type); // some sanity checking
|
||||
}
|
||||
|
||||
// set the getter
|
||||
if (typeof proto['get_' + name] === 'undefined') {
|
||||
proto['get_' + name] = getter;
|
||||
}
|
||||
|
||||
// set the setter if needed
|
||||
if (typeof proto['set_' + name] === 'undefined') {
|
||||
if (mode === 'rw') {
|
||||
proto['set_' + name] = setter;
|
||||
} else if (mode === 'wo') {
|
||||
proto['set_' + name] = function (val, idx) {
|
||||
if (typeof this['_' + name] !== 'undefined') {
|
||||
throw new Error(name + " can only be set once");
|
||||
}
|
||||
setter.call(this, val, idx);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// make a special setter that we can use in set defaults
|
||||
proto['_raw_set_' + name] = function (val, idx) {
|
||||
setter.call(this, val, idx);
|
||||
//delete this['_init_set_' + name]; // remove it after use
|
||||
};
|
||||
};
|
||||
|
||||
Util.make_properties = function (constructor, arr) {
|
||||
"use strict";
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
Util.make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
|
||||
}
|
||||
};
|
||||
|
||||
Util.set_defaults = function (obj, conf, defaults) {
|
||||
var defaults_keys = Object.keys(defaults);
|
||||
var conf_keys = Object.keys(conf);
|
||||
var keys_obj = {};
|
||||
var i;
|
||||
for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
|
||||
for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
|
||||
var keys = Object.keys(keys_obj);
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
var setter = obj['_raw_set_' + keys[i]];
|
||||
if (!setter) {
|
||||
Util.Warn('Invalid property ' + keys[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keys[i] in conf) {
|
||||
setter.call(obj, conf[keys[i]]);
|
||||
} else {
|
||||
setter.call(obj, defaults[keys[i]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Decode from UTF-8
|
||||
*/
|
||||
Util.decodeUTF8 = function (utf8string) {
|
||||
"use strict";
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Cross-browser routines
|
||||
*/
|
||||
|
||||
Util.getPointerEvent = function (e) {
|
||||
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
|
||||
};
|
||||
|
||||
Util.stopEvent = function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Touch detection
|
||||
Util.isTouchDevice = ('ontouchstart' in document.documentElement) ||
|
||||
// requried for Chrome debugger
|
||||
(document.ontouchstart !== undefined) ||
|
||||
// required for MS Surface
|
||||
(navigator.maxTouchPoints > 0) ||
|
||||
(navigator.msMaxTouchPoints > 0);
|
||||
window.addEventListener('touchstart', function onFirstTouch() {
|
||||
Util.isTouchDevice = true;
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
}, false);
|
||||
|
||||
Util._cursor_uris_supported = null;
|
||||
|
||||
Util.browserSupportsCursorURIs = function () {
|
||||
if (Util._cursor_uris_supported === null) {
|
||||
try {
|
||||
var target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
|
||||
if (target.style.cursor) {
|
||||
Util.Info("Data URI scheme cursor supported");
|
||||
Util._cursor_uris_supported = true;
|
||||
} else {
|
||||
Util.Warn("Data URI scheme cursor not supported");
|
||||
Util._cursor_uris_supported = false;
|
||||
}
|
||||
} catch (exc) {
|
||||
Util.Error("Data URI scheme cursor test exception: " + exc);
|
||||
Util._cursor_uris_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Util._cursor_uris_supported;
|
||||
};
|
||||
|
||||
// Set browser engine versions. Based on mootools.
|
||||
Util.Features = {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)};
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
// 'presto': (function () { return (!window.opera) ? false : true; }()),
|
||||
var detectPresto = function () {
|
||||
return !!window.opera;
|
||||
};
|
||||
|
||||
// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
|
||||
var detectTrident = function () {
|
||||
if (!window.ActiveXObject) {
|
||||
return false;
|
||||
} else {
|
||||
if (window.XMLHttpRequest) {
|
||||
return (document.querySelectorAll) ? 6 : 5;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Util.Features.xpath) ? ((Util.Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
|
||||
var detectInitialWebkit = function () {
|
||||
try {
|
||||
if (navigator.taintEnabled) {
|
||||
return false;
|
||||
} else {
|
||||
if (Util.Features.xpath) {
|
||||
return (Util.Features.query) ? 525 : 420;
|
||||
} else {
|
||||
return 419;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var detectActualWebkit = function (initial_ver) {
|
||||
var re = /WebKit\/([0-9\.]*) /;
|
||||
var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
|
||||
return parseFloat(str_ver, 10);
|
||||
};
|
||||
|
||||
// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
|
||||
var detectGecko = function () {
|
||||
/* jshint -W041 */
|
||||
if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
|
||||
return false;
|
||||
} else {
|
||||
return (document.getElementsByClassName) ? 19 : 18;
|
||||
}
|
||||
/* jshint +W041 */
|
||||
};
|
||||
|
||||
Util.Engine = {
|
||||
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
|
||||
//'presto': (function() {
|
||||
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
'presto': detectPresto(),
|
||||
'trident': detectTrident(),
|
||||
'webkit': detectInitialWebkit(),
|
||||
'gecko': detectGecko()
|
||||
};
|
||||
|
||||
if (Util.Engine.webkit) {
|
||||
// Extract actual webkit version if available
|
||||
Util.Engine.webkit = detectActualWebkit(Util.Engine.webkit);
|
||||
}
|
||||
})();
|
||||
|
||||
Util.Flash = (function () {
|
||||
"use strict";
|
||||
var v, version;
|
||||
try {
|
||||
v = navigator.plugins['Shockwave Flash'].description;
|
||||
} catch (err1) {
|
||||
try {
|
||||
v = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
|
||||
} catch (err2) {
|
||||
v = '0 r0';
|
||||
}
|
||||
}
|
||||
version = v.match(/\d+/g);
|
||||
return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
|
||||
}());
|
||||
|
||||
|
||||
Util.Localisation = {
|
||||
// Currently configured language
|
||||
language: 'en',
|
||||
|
||||
// Current dictionary of translations
|
||||
dictionary: undefined,
|
||||
|
||||
// Configure suitable language based on user preferences
|
||||
setup: function (supportedLanguages) {
|
||||
var userLanguages;
|
||||
|
||||
Util.Localisation.language = 'en'; // Default: US English
|
||||
|
||||
/*
|
||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||
* Fall back to navigator.language for other browsers
|
||||
*/
|
||||
if (typeof window.navigator.languages == 'object') {
|
||||
userLanguages = window.navigator.languages;
|
||||
} else {
|
||||
userLanguages = [navigator.language || navigator.userLanguage];
|
||||
}
|
||||
|
||||
for (var i = 0;i < userLanguages.length;i++) {
|
||||
var userLang = userLanguages[i];
|
||||
userLang = userLang.toLowerCase();
|
||||
userLang = userLang.replace("_", "-");
|
||||
userLang = userLang.split("-");
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en') &&
|
||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (var j = 0;j < supportedLanguages.length;j++) {
|
||||
var supLang = supportedLanguages[j];
|
||||
supLang = supLang.toLowerCase();
|
||||
supLang = supLang.replace("_", "-");
|
||||
supLang = supLang.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (userLang[1] !== supLang[1])
|
||||
continue;
|
||||
|
||||
Util.Localisation.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
for (var j = 0;j < supportedLanguages.length;j++) {
|
||||
supLang = supportedLanguages[j];
|
||||
supLang = supLang.toLowerCase();
|
||||
supLang = supLang.replace("_", "-");
|
||||
supLang = supLang.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (supLang[1] !== undefined)
|
||||
continue;
|
||||
|
||||
Util.Localisation.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Retrieve localised text
|
||||
get: function (id) {
|
||||
if (typeof Util.Localisation.dictionary !== 'undefined' && Util.Localisation.dictionary[id]) {
|
||||
return Util.Localisation.dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
},
|
||||
|
||||
// Traverses the DOM and translates relevant fields
|
||||
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
|
||||
translateDOM: function () {
|
||||
function process(elem, enabled) {
|
||||
function isAnyOf(searchElement, items) {
|
||||
return items.indexOf(searchElement) !== -1;
|
||||
}
|
||||
|
||||
function translateAttribute(elem, attr) {
|
||||
var str = elem.getAttribute(attr);
|
||||
str = Util.Localisation.get(str);
|
||||
elem.setAttribute(attr, str);
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
var str = node.data.trim();
|
||||
str = Util.Localisation.get(str);
|
||||
node.data = str;
|
||||
}
|
||||
|
||||
if (elem.hasAttribute("translate")) {
|
||||
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
|
||||
enabled = true;
|
||||
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (elem.hasAttribute("abbr") &&
|
||||
elem.tagName === "TH") {
|
||||
translateAttribute(elem, "abbr");
|
||||
}
|
||||
if (elem.hasAttribute("alt") &&
|
||||
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
|
||||
translateAttribute(elem, "alt");
|
||||
}
|
||||
if (elem.hasAttribute("download") &&
|
||||
isAnyOf(elem.tagName, ["A", "AREA"])) {
|
||||
translateAttribute(elem, "download");
|
||||
}
|
||||
if (elem.hasAttribute("label") &&
|
||||
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
|
||||
"OPTION", "TRACK"])) {
|
||||
translateAttribute(elem, "label");
|
||||
}
|
||||
// FIXME: Should update "lang"
|
||||
if (elem.hasAttribute("placeholder") &&
|
||||
isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
|
||||
translateAttribute(elem, "placeholder");
|
||||
}
|
||||
if (elem.hasAttribute("title")) {
|
||||
translateAttribute(elem, "title");
|
||||
}
|
||||
if (elem.hasAttribute("value") &&
|
||||
elem.tagName === "INPUT" &&
|
||||
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
|
||||
translateAttribute(elem, "value");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0;i < elem.childNodes.length;i++) {
|
||||
let node = elem.childNodes[i];
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
translateTextNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process(document.body, true);
|
||||
},
|
||||
};
|
||||
|
||||
// Emulate Element.setCapture() when not supported
|
||||
|
||||
Util._captureRecursion = false;
|
||||
Util._captureProxy = function (e) {
|
||||
// Recursion protection as we'll see our own event
|
||||
if (Util._captureRecursion) return;
|
||||
|
||||
// Clone the event as we cannot dispatch an already dispatched event
|
||||
var newEv = new e.constructor(e.type, e);
|
||||
|
||||
Util._captureRecursion = true;
|
||||
Util._captureElem.dispatchEvent(newEv);
|
||||
Util._captureRecursion = false;
|
||||
|
||||
// Avoid double events
|
||||
e.stopPropagation();
|
||||
|
||||
// Respect the wishes of the redirected event handlers
|
||||
if (newEv.defaultPrevented) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Implicitly release the capture on button release
|
||||
if ((e.type === "mouseup") || (e.type === "touchend")) {
|
||||
Util.releaseCapture();
|
||||
}
|
||||
};
|
||||
|
||||
// Follow cursor style of target element
|
||||
Util._captureElemChanged = function() {
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.cursor = window.getComputedStyle(Util._captureElem).cursor;
|
||||
};
|
||||
Util._captureObserver = new MutationObserver(Util._captureElemChanged);
|
||||
|
||||
Util._captureIndex = 0;
|
||||
|
||||
Util.setCapture = function (elem) {
|
||||
if (elem.setCapture) {
|
||||
|
||||
elem.setCapture();
|
||||
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', Util.releaseCapture);
|
||||
elem.addEventListener('touchend', Util.releaseCapture);
|
||||
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
Util.releaseCapture();
|
||||
|
||||
// Safari on iOS 9 has a broken constructor for TouchEvent.
|
||||
// We are fine in this case however, since Safari seems to
|
||||
// have some sort of implicit setCapture magic anyway.
|
||||
if (window.TouchEvent !== undefined) {
|
||||
try {
|
||||
new TouchEvent("touchstart");
|
||||
} catch (TypeError) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement("div");
|
||||
captureElem.id = "noVNC_mouse_capture_elem";
|
||||
captureElem.style.position = "fixed";
|
||||
captureElem.style.top = "0px";
|
||||
captureElem.style.left = "0px";
|
||||
captureElem.style.width = "100%";
|
||||
captureElem.style.height = "100%";
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = "none";
|
||||
document.body.appendChild(captureElem);
|
||||
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', Util._captureProxy);
|
||||
|
||||
captureElem.addEventListener('mousemove', Util._captureProxy);
|
||||
captureElem.addEventListener('mouseup', Util._captureProxy);
|
||||
|
||||
captureElem.addEventListener('touchmove', Util._captureProxy);
|
||||
captureElem.addEventListener('touchend', Util._captureProxy);
|
||||
}
|
||||
|
||||
Util._captureElem = elem;
|
||||
Util._captureIndex++;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
Util._captureObserver.observe(elem, {attributes:true});
|
||||
Util._captureElemChanged();
|
||||
|
||||
captureElem.style.display = null;
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
window.addEventListener('mousemove', Util._captureProxy);
|
||||
window.addEventListener('mouseup', Util._captureProxy);
|
||||
|
||||
window.addEventListener('touchmove', Util._captureProxy);
|
||||
window.addEventListener('touchend', Util._captureProxy);
|
||||
}
|
||||
};
|
||||
|
||||
Util.releaseCapture = function () {
|
||||
if (document.releaseCapture) {
|
||||
|
||||
document.releaseCapture();
|
||||
|
||||
} else {
|
||||
if (!Util._captureElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout(function(expected) {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (Util._captureIndex === expected) {
|
||||
Util._captureElem = null;
|
||||
}
|
||||
}, 0, Util._captureIndex);
|
||||
|
||||
Util._captureObserver.disconnect();
|
||||
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.display = "none";
|
||||
|
||||
window.removeEventListener('mousemove', Util._captureProxy);
|
||||
window.removeEventListener('mouseup', Util._captureProxy);
|
||||
|
||||
window.removeEventListener('touchmove', Util._captureProxy);
|
||||
window.removeEventListener('touchend', Util._captureProxy);
|
||||
}
|
||||
};
|
||||
|
||||
export default Util;
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
import * as Log from './logging.js';
|
||||
|
||||
// Set browser engine versions. Based on mootools.
|
||||
const Features = {xpath: !!(document.evaluate), query: !!(document.querySelector)};
|
||||
|
||||
// 'presto': (function () { return (!window.opera) ? false : true; }()),
|
||||
var detectPresto = function () {
|
||||
return !!window.opera;
|
||||
};
|
||||
|
||||
// 'trident': (function () { return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
|
||||
var detectTrident = function () {
|
||||
if (!window.ActiveXObject) {
|
||||
return false;
|
||||
} else {
|
||||
if (window.XMLHttpRequest) {
|
||||
return (document.querySelectorAll) ? 6 : 5;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 'webkit': (function () { try { return (navigator.taintEnabled) ? false : ((Features.xpath) ? ((Features.query) ? 525 : 420) : 419); } catch (e) { return false; } }()),
|
||||
var detectInitialWebkit = function () {
|
||||
try {
|
||||
if (navigator.taintEnabled) {
|
||||
return false;
|
||||
} else {
|
||||
if (Features.xpath) {
|
||||
return (Features.query) ? 525 : 420;
|
||||
} else {
|
||||
return 419;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var detectActualWebkit = function (initial_ver) {
|
||||
var re = /WebKit\/([0-9\.]*) /;
|
||||
var str_ver = (navigator.userAgent.match(re) || ['', initial_ver])[1];
|
||||
return parseFloat(str_ver, 10);
|
||||
};
|
||||
|
||||
// 'gecko': (function () { return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19ssName) ? 19 : 18 : 18); }())
|
||||
var detectGecko = function () {
|
||||
/* jshint -W041 */
|
||||
if (!document.getBoxObjectFor && window.mozInnerScreenX == null) {
|
||||
return false;
|
||||
} else {
|
||||
return (document.getElementsByClassName) ? 19 : 18;
|
||||
}
|
||||
/* jshint +W041 */
|
||||
};
|
||||
|
||||
const isWebkitInitial = detectInitialWebkit();
|
||||
|
||||
export const Engine = {
|
||||
// Version detection break in Opera 11.60 (errors on arguments.callee.caller reference)
|
||||
//'presto': (function() {
|
||||
// return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925)); }()),
|
||||
'presto': detectPresto(),
|
||||
'trident': detectTrident(),
|
||||
'webkit': isWebkitInitial ? detectActualWebkit(isWebkitInitial) : false,
|
||||
'gecko': detectGecko()
|
||||
};
|
||||
|
||||
// Touch detection
|
||||
export var isTouchDevice = ('ontouchstart' in document.documentElement) ||
|
||||
// requried for Chrome debugger
|
||||
(document.ontouchstart !== undefined) ||
|
||||
// required for MS Surface
|
||||
(navigator.maxTouchPoints > 0) ||
|
||||
(navigator.msMaxTouchPoints > 0);
|
||||
window.addEventListener('touchstart', function onFirstTouch() {
|
||||
isTouchDevice = true;
|
||||
window.removeEventListener('touchstart', onFirstTouch, false);
|
||||
}, false);
|
||||
|
||||
var _cursor_uris_supported = null;
|
||||
|
||||
export function browserSupportsCursorURIs () {
|
||||
if (_cursor_uris_supported === null) {
|
||||
try {
|
||||
var target = document.createElement('canvas');
|
||||
target.style.cursor = 'url("data:image/x-icon;base64,AAACAAEACAgAAAIAAgA4AQAAFgAAACgAAAAIAAAAEAAAAAEAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAAAAAAAAAAAAAAAAAAAA==") 2 2, default';
|
||||
|
||||
if (target.style.cursor) {
|
||||
Log.Info("Data URI scheme cursor supported");
|
||||
_cursor_uris_supported = true;
|
||||
} else {
|
||||
Log.Warn("Data URI scheme cursor not supported");
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
} catch (exc) {
|
||||
Log.Error("Data URI scheme cursor test exception: " + exc);
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
return _cursor_uris_supported;
|
||||
};
|
||||
|
||||
export function _forceCursorURIs(enabled) {
|
||||
if (enabled === undefined || enabled) {
|
||||
_cursor_uris_supported = true;
|
||||
} else {
|
||||
_cursor_uris_supported = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cross-browser event and position routines
|
||||
*/
|
||||
|
||||
import * as Log from './logging.js';
|
||||
|
||||
export function getPointerEvent (e) {
|
||||
return e.changedTouches ? e.changedTouches[0] : e.touches ? e.touches[0] : e;
|
||||
};
|
||||
|
||||
export function stopEvent (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// Emulate Element.setCapture() when not supported
|
||||
var _captureRecursion = false;
|
||||
var _captureElem = null;
|
||||
const _captureProxy = function (e) {
|
||||
// Recursion protection as we'll see our own event
|
||||
if (_captureRecursion) return;
|
||||
|
||||
// Clone the event as we cannot dispatch an already dispatched event
|
||||
var newEv = new e.constructor(e.type, e);
|
||||
|
||||
_captureRecursion = true;
|
||||
_captureElem.dispatchEvent(newEv);
|
||||
_captureRecursion = false;
|
||||
|
||||
// Avoid double events
|
||||
e.stopPropagation();
|
||||
|
||||
// Respect the wishes of the redirected event handlers
|
||||
if (newEv.defaultPrevented) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// Implicitly release the capture on button release
|
||||
if ((e.type === "mouseup") || (e.type === "touchend")) {
|
||||
releaseCapture();
|
||||
}
|
||||
};
|
||||
|
||||
// Follow cursor style of target element
|
||||
const _captureElemChanged = function() {
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.cursor = window.getComputedStyle(_captureElem).cursor;
|
||||
};
|
||||
const _captureObserver = new MutationObserver(_captureElemChanged);
|
||||
|
||||
var _captureIndex = 0;
|
||||
|
||||
export function setCapture (elem) {
|
||||
if (elem.setCapture) {
|
||||
|
||||
elem.setCapture();
|
||||
|
||||
// IE releases capture on 'click' events which might not trigger
|
||||
elem.addEventListener('mouseup', releaseCapture);
|
||||
elem.addEventListener('touchend', releaseCapture);
|
||||
|
||||
} else {
|
||||
// Release any existing capture in case this method is
|
||||
// called multiple times without coordination
|
||||
releaseCapture();
|
||||
|
||||
// Safari on iOS 9 has a broken constructor for TouchEvent.
|
||||
// We are fine in this case however, since Safari seems to
|
||||
// have some sort of implicit setCapture magic anyway.
|
||||
if (window.TouchEvent !== undefined) {
|
||||
try {
|
||||
new TouchEvent("touchstart");
|
||||
} catch (TypeError) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
|
||||
if (captureElem === null) {
|
||||
captureElem = document.createElement("div");
|
||||
captureElem.id = "noVNC_mouse_capture_elem";
|
||||
captureElem.style.position = "fixed";
|
||||
captureElem.style.top = "0px";
|
||||
captureElem.style.left = "0px";
|
||||
captureElem.style.width = "100%";
|
||||
captureElem.style.height = "100%";
|
||||
captureElem.style.zIndex = 10000;
|
||||
captureElem.style.display = "none";
|
||||
document.body.appendChild(captureElem);
|
||||
|
||||
// This is to make sure callers don't get confused by having
|
||||
// our blocking element as the target
|
||||
captureElem.addEventListener('contextmenu', _captureProxy);
|
||||
|
||||
captureElem.addEventListener('mousemove', _captureProxy);
|
||||
captureElem.addEventListener('mouseup', _captureProxy);
|
||||
|
||||
captureElem.addEventListener('touchmove', _captureProxy);
|
||||
captureElem.addEventListener('touchend', _captureProxy);
|
||||
}
|
||||
|
||||
_captureElem = elem;
|
||||
_captureIndex++;
|
||||
|
||||
// Track cursor and get initial cursor
|
||||
_captureObserver.observe(elem, {attributes:true});
|
||||
_captureElemChanged();
|
||||
|
||||
captureElem.style.display = null;
|
||||
|
||||
// We listen to events on window in order to keep tracking if it
|
||||
// happens to leave the viewport
|
||||
window.addEventListener('mousemove', _captureProxy);
|
||||
window.addEventListener('mouseup', _captureProxy);
|
||||
|
||||
window.addEventListener('touchmove', _captureProxy);
|
||||
window.addEventListener('touchend', _captureProxy);
|
||||
}
|
||||
};
|
||||
|
||||
export function releaseCapture () {
|
||||
if (document.releaseCapture) {
|
||||
|
||||
document.releaseCapture();
|
||||
|
||||
} else {
|
||||
if (!_captureElem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There might be events already queued, so we need to wait for
|
||||
// them to flush. E.g. contextmenu in Microsoft Edge
|
||||
window.setTimeout(function(expected) {
|
||||
// Only clear it if it's the expected grab (i.e. no one
|
||||
// else has initiated a new grab)
|
||||
if (_captureIndex === expected) {
|
||||
_captureElem = null;
|
||||
}
|
||||
}, 0, _captureIndex);
|
||||
|
||||
_captureObserver.disconnect();
|
||||
|
||||
var captureElem = document.getElementById("noVNC_mouse_capture_elem");
|
||||
captureElem.style.display = "none";
|
||||
|
||||
window.removeEventListener('mousemove', _captureProxy);
|
||||
window.removeEventListener('mouseup', _captureProxy);
|
||||
|
||||
window.removeEventListener('touchmove', _captureProxy);
|
||||
window.removeEventListener('touchend', _captureProxy);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Localization Utilities
|
||||
*/
|
||||
|
||||
export function Localizer() {
|
||||
// Currently configured language
|
||||
this.language = 'en';
|
||||
|
||||
// Current dictionary of translations
|
||||
this.dictionary = undefined;
|
||||
}
|
||||
|
||||
Localizer.prototype = {
|
||||
// Configure suitable language based on user preferences
|
||||
setup: function (supportedLanguages) {
|
||||
var userLanguages;
|
||||
|
||||
this.language = 'en'; // Default: US English
|
||||
|
||||
/*
|
||||
* Navigator.languages only available in Chrome (32+) and FireFox (32+)
|
||||
* Fall back to navigator.language for other browsers
|
||||
*/
|
||||
if (typeof window.navigator.languages == 'object') {
|
||||
userLanguages = window.navigator.languages;
|
||||
} else {
|
||||
userLanguages = [navigator.language || navigator.userLanguage];
|
||||
}
|
||||
|
||||
for (var i = 0;i < userLanguages.length;i++) {
|
||||
var userLang = userLanguages[i];
|
||||
userLang = userLang.toLowerCase();
|
||||
userLang = userLang.replace("_", "-");
|
||||
userLang = userLang.split("-");
|
||||
|
||||
// Built-in default?
|
||||
if ((userLang[0] === 'en') &&
|
||||
((userLang[1] === undefined) || (userLang[1] === 'us'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: perfect match
|
||||
for (var j = 0;j < supportedLanguages.length;j++) {
|
||||
var supLang = supportedLanguages[j];
|
||||
supLang = supLang.toLowerCase();
|
||||
supLang = supLang.replace("_", "-");
|
||||
supLang = supLang.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (userLang[1] !== supLang[1])
|
||||
continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: fallback
|
||||
for (var j = 0;j < supportedLanguages.length;j++) {
|
||||
supLang = supportedLanguages[j];
|
||||
supLang = supLang.toLowerCase();
|
||||
supLang = supLang.replace("_", "-");
|
||||
supLang = supLang.split("-");
|
||||
|
||||
if (userLang[0] !== supLang[0])
|
||||
continue;
|
||||
if (supLang[1] !== undefined)
|
||||
continue;
|
||||
|
||||
this.language = supportedLanguages[j];
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Retrieve localised text
|
||||
get: function (id) {
|
||||
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
|
||||
return this.dictionary[id];
|
||||
} else {
|
||||
return id;
|
||||
}
|
||||
},
|
||||
|
||||
// Traverses the DOM and translates relevant fields
|
||||
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
|
||||
translateDOM: function () {
|
||||
var self = this;
|
||||
function process(elem, enabled) {
|
||||
function isAnyOf(searchElement, items) {
|
||||
return items.indexOf(searchElement) !== -1;
|
||||
}
|
||||
|
||||
function translateAttribute(elem, attr) {
|
||||
var str = elem.getAttribute(attr);
|
||||
str = self.get(str);
|
||||
elem.setAttribute(attr, str);
|
||||
}
|
||||
|
||||
function translateTextNode(node) {
|
||||
var str = node.data.trim();
|
||||
str = self.get(str);
|
||||
node.data = str;
|
||||
}
|
||||
|
||||
if (elem.hasAttribute("translate")) {
|
||||
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
|
||||
enabled = true;
|
||||
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
if (elem.hasAttribute("abbr") &&
|
||||
elem.tagName === "TH") {
|
||||
translateAttribute(elem, "abbr");
|
||||
}
|
||||
if (elem.hasAttribute("alt") &&
|
||||
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
|
||||
translateAttribute(elem, "alt");
|
||||
}
|
||||
if (elem.hasAttribute("download") &&
|
||||
isAnyOf(elem.tagName, ["A", "AREA"])) {
|
||||
translateAttribute(elem, "download");
|
||||
}
|
||||
if (elem.hasAttribute("label") &&
|
||||
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
|
||||
"OPTION", "TRACK"])) {
|
||||
translateAttribute(elem, "label");
|
||||
}
|
||||
// FIXME: Should update "lang"
|
||||
if (elem.hasAttribute("placeholder") &&
|
||||
isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
|
||||
translateAttribute(elem, "placeholder");
|
||||
}
|
||||
if (elem.hasAttribute("title")) {
|
||||
translateAttribute(elem, "title");
|
||||
}
|
||||
if (elem.hasAttribute("value") &&
|
||||
elem.tagName === "INPUT" &&
|
||||
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
|
||||
translateAttribute(elem, "value");
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0;i < elem.childNodes.length;i++) {
|
||||
let node = elem.childNodes[i];
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
process(node, enabled);
|
||||
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
||||
translateTextNode(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
process(document.body, true);
|
||||
},
|
||||
}
|
||||
|
||||
export const l10n = new Localizer();
|
||||
export default l10n.get.bind(l10n);
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Logging/debug routines
|
||||
*/
|
||||
|
||||
var _log_level = 'warn';
|
||||
|
||||
var Debug = function (msg) {};
|
||||
var Info = function (msg) {};
|
||||
var Warn = function (msg) {};
|
||||
var Error = function (msg) {};
|
||||
|
||||
export function init_logging (level) {
|
||||
if (typeof level === 'undefined') {
|
||||
level = _log_level;
|
||||
} else {
|
||||
_log_level = level;
|
||||
}
|
||||
|
||||
Debug = Info = Warn = Error = function (msg) {};
|
||||
if (typeof window.console !== "undefined") {
|
||||
/* jshint -W086 */
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
Debug = console.debug.bind(window.console);
|
||||
case 'info':
|
||||
Info = console.info.bind(window.console);
|
||||
case 'warn':
|
||||
Warn = console.warn.bind(window.console);
|
||||
case 'error':
|
||||
Error = console.error.bind(window.console);
|
||||
case 'none':
|
||||
break;
|
||||
default:
|
||||
throw new Error("invalid logging type '" + level + "'");
|
||||
}
|
||||
/* jshint +W086 */
|
||||
}
|
||||
};
|
||||
export function get_logging () {
|
||||
return _log_level;
|
||||
};
|
||||
export { Debug, Info, Warn, Error };
|
||||
|
||||
// Initialize logging level
|
||||
init_logging();
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Getter/Setter Creation Utilities
|
||||
*/
|
||||
|
||||
import * as Log from './logging.js';
|
||||
|
||||
function make_property (proto, name, mode, type) {
|
||||
"use strict";
|
||||
|
||||
var getter;
|
||||
if (type === 'arr') {
|
||||
getter = function (idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
return this['_' + name][idx];
|
||||
} else {
|
||||
return this['_' + name];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
getter = function () {
|
||||
return this['_' + name];
|
||||
};
|
||||
}
|
||||
|
||||
var make_setter = function (process_val) {
|
||||
if (process_val) {
|
||||
return function (val, idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
this['_' + name][idx] = process_val(val);
|
||||
} else {
|
||||
this['_' + name] = process_val(val);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return function (val, idx) {
|
||||
if (typeof idx !== 'undefined') {
|
||||
this['_' + name][idx] = val;
|
||||
} else {
|
||||
this['_' + name] = val;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
var setter;
|
||||
if (type === 'bool') {
|
||||
setter = make_setter(function (val) {
|
||||
if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else if (type === 'int') {
|
||||
setter = make_setter(function (val) { return parseInt(val, 10); });
|
||||
} else if (type === 'float') {
|
||||
setter = make_setter(parseFloat);
|
||||
} else if (type === 'str') {
|
||||
setter = make_setter(String);
|
||||
} else if (type === 'func') {
|
||||
setter = make_setter(function (val) {
|
||||
if (!val) {
|
||||
return function () {};
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
} else if (type === 'arr' || type === 'dom' || type == 'raw') {
|
||||
setter = make_setter();
|
||||
} else {
|
||||
throw new Error('Unknown property type ' + type); // some sanity checking
|
||||
}
|
||||
|
||||
// set the getter
|
||||
if (typeof proto['get_' + name] === 'undefined') {
|
||||
proto['get_' + name] = getter;
|
||||
}
|
||||
|
||||
// set the setter if needed
|
||||
if (typeof proto['set_' + name] === 'undefined') {
|
||||
if (mode === 'rw') {
|
||||
proto['set_' + name] = setter;
|
||||
} else if (mode === 'wo') {
|
||||
proto['set_' + name] = function (val, idx) {
|
||||
if (typeof this['_' + name] !== 'undefined') {
|
||||
throw new Error(name + " can only be set once");
|
||||
}
|
||||
setter.call(this, val, idx);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// make a special setter that we can use in set defaults
|
||||
proto['_raw_set_' + name] = function (val, idx) {
|
||||
setter.call(this, val, idx);
|
||||
//delete this['_init_set_' + name]; // remove it after use
|
||||
};
|
||||
};
|
||||
|
||||
export function make_properties (constructor, arr) {
|
||||
"use strict";
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]);
|
||||
}
|
||||
};
|
||||
|
||||
export function set_defaults (obj, conf, defaults) {
|
||||
var defaults_keys = Object.keys(defaults);
|
||||
var conf_keys = Object.keys(conf);
|
||||
var keys_obj = {};
|
||||
var i;
|
||||
for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; }
|
||||
for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; }
|
||||
var keys = Object.keys(keys_obj);
|
||||
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
var setter = obj['_raw_set_' + keys[i]];
|
||||
if (!setter) {
|
||||
Log.Warn('Invalid property ' + keys[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keys[i] in conf) {
|
||||
setter.call(obj, conf[keys[i]]);
|
||||
} else {
|
||||
setter.call(obj, defaults[keys[i]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* noVNC: HTML5 VNC client
|
||||
* Copyright (C) 2012 Joel Martin
|
||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||
*
|
||||
* See README.md for usage and integration instructions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Decode from UTF-8
|
||||
*/
|
||||
export function decodeUTF8 (utf8string) {
|
||||
"use strict";
|
||||
return decodeURIComponent(escape(utf8string));
|
||||
};
|
614
core/websock.js
614
core/websock.js
|
@ -12,8 +12,7 @@
|
|||
* read binary data off of the receive queue.
|
||||
*/
|
||||
|
||||
import Util from "./util.js";
|
||||
|
||||
import * as Log from './util/logging.js';
|
||||
|
||||
/*jslint browser: true, bitwise: true */
|
||||
/*global Util*/
|
||||
|
@ -43,313 +42,310 @@ export default function Websock() {
|
|||
};
|
||||
};
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
// this has performance issues in some versions Chromium, and
|
||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||
// at the moment. It may be valuable to turn it on in the future.
|
||||
var ENABLE_COPYWITHIN = false;
|
||||
// this has performance issues in some versions Chromium, and
|
||||
// doesn't gain a tremendous amount of performance increase in Firefox
|
||||
// at the moment. It may be valuable to turn it on in the future.
|
||||
var ENABLE_COPYWITHIN = false;
|
||||
|
||||
var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||
var MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
|
||||
|
||||
var typedArrayToString = (function () {
|
||||
// This is only for PhantomJS, which doesn't like apply-ing
|
||||
// with Typed Arrays
|
||||
try {
|
||||
var arr = new Uint8Array([1, 2, 3]);
|
||||
String.fromCharCode.apply(null, arr);
|
||||
return function (a) { return String.fromCharCode.apply(null, a); };
|
||||
} catch (ex) {
|
||||
return function (a) {
|
||||
return String.fromCharCode.apply(
|
||||
null, Array.prototype.slice.call(a));
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
Websock.prototype = {
|
||||
// Getters and Setters
|
||||
get_sQ: function () {
|
||||
return this._sQ;
|
||||
},
|
||||
|
||||
get_rQ: function () {
|
||||
return this._rQ;
|
||||
},
|
||||
|
||||
get_rQi: function () {
|
||||
return this._rQi;
|
||||
},
|
||||
|
||||
set_rQi: function (val) {
|
||||
this._rQi = val;
|
||||
},
|
||||
|
||||
// Receive Queue
|
||||
rQlen: function () {
|
||||
return this._rQlen - this._rQi;
|
||||
},
|
||||
|
||||
rQpeek8: function () {
|
||||
return this._rQ[this._rQi];
|
||||
},
|
||||
|
||||
rQshift8: function () {
|
||||
return this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQskip8: function () {
|
||||
this._rQi++;
|
||||
},
|
||||
|
||||
rQskipBytes: function (num) {
|
||||
this._rQi += num;
|
||||
},
|
||||
|
||||
// TODO(directxman12): test performance with these vs a DataView
|
||||
rQshift16: function () {
|
||||
return (this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQshift32: function () {
|
||||
return (this._rQ[this._rQi++] << 24) +
|
||||
(this._rQ[this._rQi++] << 16) +
|
||||
(this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQshiftStr: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
|
||||
this._rQi += len;
|
||||
return typedArrayToString(arr);
|
||||
},
|
||||
|
||||
rQshiftBytes: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
},
|
||||
|
||||
rQshiftTo: function (target, len) {
|
||||
if (len === undefined) { len = this.rQlen(); }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
},
|
||||
|
||||
rQwhole: function () {
|
||||
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
|
||||
},
|
||||
|
||||
rQslice: function (start, end) {
|
||||
if (end) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
} else {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
|
||||
}
|
||||
},
|
||||
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
rQwait: function (msg, num, goback) {
|
||||
var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Send Queue
|
||||
|
||||
flush: function () {
|
||||
if (this._websocket.bufferedAmount !== 0) {
|
||||
Util.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
|
||||
}
|
||||
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
},
|
||||
|
||||
send: function (arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
},
|
||||
|
||||
send_string: function (str) {
|
||||
this.send(str.split('').map(function (chr) {
|
||||
return chr.charCodeAt(0);
|
||||
}));
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
off: function (evt) {
|
||||
this._eventHandlers[evt] = function () {};
|
||||
},
|
||||
|
||||
on: function (evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
},
|
||||
|
||||
_allocate_buffers: function () {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this._allocate_buffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
},
|
||||
|
||||
open: function (uri, protocols) {
|
||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onopen = (function () {
|
||||
Util.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
Util.Info("Server choose sub-protocol: " + this._websocket.protocol);
|
||||
}
|
||||
|
||||
this._eventHandlers.open();
|
||||
Util.Debug("<< WebSock.onopen");
|
||||
}).bind(this);
|
||||
this._websocket.onclose = (function (e) {
|
||||
Util.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Util.Debug("<< WebSock.onclose");
|
||||
}).bind(this);
|
||||
this._websocket.onerror = (function (e) {
|
||||
Util.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
Util.Debug("<< WebSock.onerror: " + e);
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Util.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
},
|
||||
|
||||
// private methods
|
||||
_encode_message: function () {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
},
|
||||
|
||||
_expand_compact_rQ: function (min_fit) {
|
||||
var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
|
||||
throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
var old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
} else {
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
}
|
||||
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
},
|
||||
|
||||
_decode_message: function (data) {
|
||||
// push arraybuffer values onto the end
|
||||
var u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
},
|
||||
|
||||
_recv_message: function (e) {
|
||||
try {
|
||||
this._decode_message(e.data);
|
||||
if (this.rQlen() > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Util.Debug("Ignoring empty message");
|
||||
}
|
||||
} catch (exc) {
|
||||
var exception_str = "";
|
||||
if (exc.name) {
|
||||
exception_str += "\n name: " + exc.name + "\n";
|
||||
exception_str += " message: " + exc.message + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.description !== 'undefined') {
|
||||
exception_str += " description: " + exc.description + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.stack !== 'undefined') {
|
||||
exception_str += exc.stack;
|
||||
}
|
||||
|
||||
if (exception_str.length > 0) {
|
||||
Util.Error("recv_message, caught exception: " + exception_str);
|
||||
} else {
|
||||
Util.Error("recv_message, caught exception: " + exc);
|
||||
}
|
||||
|
||||
if (typeof exc.name !== 'undefined') {
|
||||
this._eventHandlers.error(exc.name + ": " + exc.message);
|
||||
} else {
|
||||
this._eventHandlers.error(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
var typedArrayToString = (function () {
|
||||
// This is only for PhantomJS, which doesn't like apply-ing
|
||||
// with Typed Arrays
|
||||
try {
|
||||
var arr = new Uint8Array([1, 2, 3]);
|
||||
String.fromCharCode.apply(null, arr);
|
||||
return function (a) { return String.fromCharCode.apply(null, a); };
|
||||
} catch (ex) {
|
||||
return function (a) {
|
||||
return String.fromCharCode.apply(
|
||||
null, Array.prototype.slice.call(a));
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
Websock.prototype = {
|
||||
// Getters and Setters
|
||||
get_sQ: function () {
|
||||
return this._sQ;
|
||||
},
|
||||
|
||||
get_rQ: function () {
|
||||
return this._rQ;
|
||||
},
|
||||
|
||||
get_rQi: function () {
|
||||
return this._rQi;
|
||||
},
|
||||
|
||||
set_rQi: function (val) {
|
||||
this._rQi = val;
|
||||
},
|
||||
|
||||
// Receive Queue
|
||||
rQlen: function () {
|
||||
return this._rQlen - this._rQi;
|
||||
},
|
||||
|
||||
rQpeek8: function () {
|
||||
return this._rQ[this._rQi];
|
||||
},
|
||||
|
||||
rQshift8: function () {
|
||||
return this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQskip8: function () {
|
||||
this._rQi++;
|
||||
},
|
||||
|
||||
rQskipBytes: function (num) {
|
||||
this._rQi += num;
|
||||
},
|
||||
|
||||
// TODO(directxman12): test performance with these vs a DataView
|
||||
rQshift16: function () {
|
||||
return (this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQshift32: function () {
|
||||
return (this._rQ[this._rQi++] << 24) +
|
||||
(this._rQ[this._rQi++] << 16) +
|
||||
(this._rQ[this._rQi++] << 8) +
|
||||
this._rQ[this._rQi++];
|
||||
},
|
||||
|
||||
rQshiftStr: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
var arr = new Uint8Array(this._rQ.buffer, this._rQi, len);
|
||||
this._rQi += len;
|
||||
return typedArrayToString(arr);
|
||||
},
|
||||
|
||||
rQshiftBytes: function (len) {
|
||||
if (typeof(len) === 'undefined') { len = this.rQlen(); }
|
||||
this._rQi += len;
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
|
||||
},
|
||||
|
||||
rQshiftTo: function (target, len) {
|
||||
if (len === undefined) { len = this.rQlen(); }
|
||||
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
|
||||
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
|
||||
this._rQi += len;
|
||||
},
|
||||
|
||||
rQwhole: function () {
|
||||
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
|
||||
},
|
||||
|
||||
rQslice: function (start, end) {
|
||||
if (end) {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
|
||||
} else {
|
||||
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
|
||||
}
|
||||
},
|
||||
|
||||
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
|
||||
// to be available in the receive queue. Return true if we need to
|
||||
// wait (and possibly print a debug message), otherwise false.
|
||||
rQwait: function (msg, num, goback) {
|
||||
var rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
|
||||
if (rQlen < num) {
|
||||
if (goback) {
|
||||
if (this._rQi < goback) {
|
||||
throw new Error("rQwait cannot backup " + goback + " bytes");
|
||||
}
|
||||
this._rQi -= goback;
|
||||
}
|
||||
return true; // true means need more data
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Send Queue
|
||||
|
||||
flush: function () {
|
||||
if (this._websocket.bufferedAmount !== 0) {
|
||||
Log.Debug("bufferedAmount: " + this._websocket.bufferedAmount);
|
||||
}
|
||||
|
||||
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
|
||||
this._websocket.send(this._encode_message());
|
||||
this._sQlen = 0;
|
||||
}
|
||||
},
|
||||
|
||||
send: function (arr) {
|
||||
this._sQ.set(arr, this._sQlen);
|
||||
this._sQlen += arr.length;
|
||||
this.flush();
|
||||
},
|
||||
|
||||
send_string: function (str) {
|
||||
this.send(str.split('').map(function (chr) {
|
||||
return chr.charCodeAt(0);
|
||||
}));
|
||||
},
|
||||
|
||||
// Event Handlers
|
||||
off: function (evt) {
|
||||
this._eventHandlers[evt] = function () {};
|
||||
},
|
||||
|
||||
on: function (evt, handler) {
|
||||
this._eventHandlers[evt] = handler;
|
||||
},
|
||||
|
||||
_allocate_buffers: function () {
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._sQ = new Uint8Array(this._sQbufferSize);
|
||||
},
|
||||
|
||||
init: function () {
|
||||
this._allocate_buffers();
|
||||
this._rQi = 0;
|
||||
this._websocket = null;
|
||||
},
|
||||
|
||||
open: function (uri, protocols) {
|
||||
var ws_schema = uri.match(/^([a-z]+):\/\//)[1];
|
||||
this.init();
|
||||
|
||||
this._websocket = new WebSocket(uri, protocols);
|
||||
this._websocket.binaryType = 'arraybuffer';
|
||||
|
||||
this._websocket.onmessage = this._recv_message.bind(this);
|
||||
this._websocket.onopen = (function () {
|
||||
Log.Debug('>> WebSock.onopen');
|
||||
if (this._websocket.protocol) {
|
||||
Log.Info("Server choose sub-protocol: " + this._websocket.protocol);
|
||||
}
|
||||
|
||||
this._eventHandlers.open();
|
||||
Log.Debug("<< WebSock.onopen");
|
||||
}).bind(this);
|
||||
this._websocket.onclose = (function (e) {
|
||||
Log.Debug(">> WebSock.onclose");
|
||||
this._eventHandlers.close(e);
|
||||
Log.Debug("<< WebSock.onclose");
|
||||
}).bind(this);
|
||||
this._websocket.onerror = (function (e) {
|
||||
Log.Debug(">> WebSock.onerror: " + e);
|
||||
this._eventHandlers.error(e);
|
||||
Log.Debug("<< WebSock.onerror: " + e);
|
||||
}).bind(this);
|
||||
},
|
||||
|
||||
close: function () {
|
||||
if (this._websocket) {
|
||||
if ((this._websocket.readyState === WebSocket.OPEN) ||
|
||||
(this._websocket.readyState === WebSocket.CONNECTING)) {
|
||||
Log.Info("Closing WebSocket connection");
|
||||
this._websocket.close();
|
||||
}
|
||||
|
||||
this._websocket.onmessage = function (e) { return; };
|
||||
}
|
||||
},
|
||||
|
||||
// private methods
|
||||
_encode_message: function () {
|
||||
// Put in a binary arraybuffer
|
||||
// according to the spec, you can send ArrayBufferViews with the send method
|
||||
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
|
||||
},
|
||||
|
||||
_expand_compact_rQ: function (min_fit) {
|
||||
var resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
|
||||
if (resizeNeeded) {
|
||||
if (!min_fit) {
|
||||
// just double the size if we need to do compaction
|
||||
this._rQbufferSize *= 2;
|
||||
} else {
|
||||
// otherwise, make sure we satisy rQlen - rQi + min_fit < rQbufferSize / 8
|
||||
this._rQbufferSize = (this._rQlen - this._rQi + min_fit) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't want to grow unboundedly
|
||||
if (this._rQbufferSize > MAX_RQ_GROW_SIZE) {
|
||||
this._rQbufferSize = MAX_RQ_GROW_SIZE;
|
||||
if (this._rQbufferSize - this._rQlen - this._rQi < min_fit) {
|
||||
throw new Exception("Receive Queue buffer exceeded " + MAX_RQ_GROW_SIZE + " bytes, and the new message could not fit");
|
||||
}
|
||||
}
|
||||
|
||||
if (resizeNeeded) {
|
||||
var old_rQbuffer = this._rQ.buffer;
|
||||
this._rQmax = this._rQbufferSize / 8;
|
||||
this._rQ = new Uint8Array(this._rQbufferSize);
|
||||
this._rQ.set(new Uint8Array(old_rQbuffer, this._rQi));
|
||||
} else {
|
||||
if (ENABLE_COPYWITHIN) {
|
||||
this._rQ.copyWithin(0, this._rQi);
|
||||
} else {
|
||||
this._rQ.set(new Uint8Array(this._rQ.buffer, this._rQi));
|
||||
}
|
||||
}
|
||||
|
||||
this._rQlen = this._rQlen - this._rQi;
|
||||
this._rQi = 0;
|
||||
},
|
||||
|
||||
_decode_message: function (data) {
|
||||
// push arraybuffer values onto the end
|
||||
var u8 = new Uint8Array(data);
|
||||
if (u8.length > this._rQbufferSize - this._rQlen) {
|
||||
this._expand_compact_rQ(u8.length);
|
||||
}
|
||||
this._rQ.set(u8, this._rQlen);
|
||||
this._rQlen += u8.length;
|
||||
},
|
||||
|
||||
_recv_message: function (e) {
|
||||
try {
|
||||
this._decode_message(e.data);
|
||||
if (this.rQlen() > 0) {
|
||||
this._eventHandlers.message();
|
||||
// Compact the receive queue
|
||||
if (this._rQlen == this._rQi) {
|
||||
this._rQlen = 0;
|
||||
this._rQi = 0;
|
||||
} else if (this._rQlen > this._rQmax) {
|
||||
this._expand_compact_rQ();
|
||||
}
|
||||
} else {
|
||||
Log.Debug("Ignoring empty message");
|
||||
}
|
||||
} catch (exc) {
|
||||
var exception_str = "";
|
||||
if (exc.name) {
|
||||
exception_str += "\n name: " + exc.name + "\n";
|
||||
exception_str += " message: " + exc.message + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.description !== 'undefined') {
|
||||
exception_str += " description: " + exc.description + "\n";
|
||||
}
|
||||
|
||||
if (typeof exc.stack !== 'undefined') {
|
||||
exception_str += exc.stack;
|
||||
}
|
||||
|
||||
if (exception_str.length > 0) {
|
||||
Log.Error("recv_message, caught exception: " + exception_str);
|
||||
} else {
|
||||
Log.Error("recv_message, caught exception: " + exc);
|
||||
}
|
||||
|
||||
if (typeof exc.name !== 'undefined') {
|
||||
this._eventHandlers.error(exc.name + ": " + exc.message);
|
||||
} else {
|
||||
this._eventHandlers.error(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue