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:
Solly Ross 2017-02-04 21:26:00 -05:00
parent 6e744119f8
commit 6d6f0db0da
21 changed files with 5806 additions and 5838 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ utils/websockify
/build /build
/lib /lib
recordings recordings
*.swp
*~

View File

@ -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 Javascript code necessary for full noVNC operation. This includes (but
is not limited to): is not limited to):
core/base64.js core/**/*.js
core/des.js app/*.js
core/display.js test/playback.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
The HTML, CSS, font and images files that included with the noVNC The HTML, CSS, font and images files that included with the noVNC
source distibution (or repository) are not considered part of the source distibution (or repository) are not considered part of the

2813
app/ui.js

File diff suppressed because it is too large Load Diff

View File

@ -10,31 +10,21 @@
/*jslint bitwise: false, white: false, browser: true, devel: true */ /*jslint bitwise: false, white: false, browser: true, devel: true */
/*global Util, window, document */ /*global Util, window, document */
import Util from "../core/util.js"; import { init_logging as main_init_logging } from '../core/util/logging.js';
// Globals defined here
var WebUtil = {};
/*
* ------------------------------------------------------
* Namespaced in WebUtil
* ------------------------------------------------------
*/
// init log level reading the logging HTTP param // init log level reading the logging HTTP param
WebUtil.init_logging = function (level) { export function init_logging (level) {
"use strict"; "use strict";
if (typeof level !== "undefined") { if (typeof level !== "undefined") {
Util._log_level = level; main_init_logging(level);
} else { } else {
var param = document.location.href.match(/logging=([A-Za-z0-9\._\-]*)/); 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 // Read a query string variable
WebUtil.getQueryVar = function (name, defVal) { export function getQueryVar (name, defVal) {
"use strict"; "use strict";
var re = new RegExp('.*[?&]' + name + '=([^&#]*)'), var re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
match = document.location.href.match(re); match = document.location.href.match(re);
@ -47,7 +37,7 @@ WebUtil.getQueryVar = function (name, defVal) {
}; };
// Read a hash fragment variable // Read a hash fragment variable
WebUtil.getHashVar = function (name, defVal) { export function getHashVar (name, defVal) {
"use strict"; "use strict";
var re = new RegExp('.*[&#]' + name + '=([^&]*)'), var re = new RegExp('.*[&#]' + name + '=([^&]*)'),
match = document.location.hash.match(re); 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 // Read a variable from the fragment or the query string
// Fragment takes precedence // Fragment takes precedence
WebUtil.getConfigVar = function (name, defVal) { export function getConfigVar (name, defVal) {
"use strict"; "use strict";
var val = WebUtil.getHashVar(name); var val = getHashVar(name);
if (val === null) { if (val === null) {
val = WebUtil.getQueryVar(name, defVal); val = getQueryVar(name, defVal);
} }
return val; return val;
}; };
@ -75,7 +65,7 @@ WebUtil.getConfigVar = function (name, defVal) {
*/ */
// No days means only for this browser session // No days means only for this browser session
WebUtil.createCookie = function (name, value, days) { export function createCookie (name, value, days) {
"use strict"; "use strict";
var date, expires; var date, expires;
if (days) { if (days) {
@ -95,7 +85,7 @@ WebUtil.createCookie = function (name, value, days) {
document.cookie = name + "=" + value + expires + "; path=/" + secure; document.cookie = name + "=" + value + expires + "; path=/" + secure;
}; };
WebUtil.readCookie = function (name, defaultValue) { export function readCookie (name, defaultValue) {
"use strict"; "use strict";
var nameEQ = name + "=", var nameEQ = name + "=",
ca = document.cookie.split(';'); ca = document.cookie.split(';');
@ -108,22 +98,24 @@ WebUtil.readCookie = function (name, defaultValue) {
return (typeof defaultValue !== 'undefined') ? defaultValue : null; return (typeof defaultValue !== 'undefined') ? defaultValue : null;
}; };
WebUtil.eraseCookie = function (name) { export function eraseCookie (name) {
"use strict"; "use strict";
WebUtil.createCookie(name, "", -1); createCookie(name, "", -1);
}; };
/* /*
* Setting handling. * Setting handling.
*/ */
WebUtil.initSettings = function (callback /*, ...callbackArgs */) { var settings = {};
export function initSettings (callback /*, ...callbackArgs */) {
"use strict"; "use strict";
var callbackArgs = Array.prototype.slice.call(arguments, 1); var callbackArgs = Array.prototype.slice.call(arguments, 1);
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.get(function (cfg) { window.chrome.storage.sync.get(function (cfg) {
WebUtil.settings = cfg; settings = cfg;
console.log(WebUtil.settings); console.log(settings);
if (callback) { if (callback) {
callback.apply(this, callbackArgs); callback.apply(this, callbackArgs);
} }
@ -137,24 +129,24 @@ WebUtil.initSettings = function (callback /*, ...callbackArgs */) {
}; };
// No days means only for this browser session // No days means only for this browser session
WebUtil.writeSetting = function (name, value) { export function writeSetting (name, value) {
"use strict"; "use strict";
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
//console.log("writeSetting:", name, value); //console.log("writeSetting:", name, value);
if (WebUtil.settings[name] !== value) { if (settings[name] !== value) {
WebUtil.settings[name] = value; settings[name] = value;
window.chrome.storage.sync.set(WebUtil.settings); window.chrome.storage.sync.set(settings);
} }
} else { } else {
localStorage.setItem(name, value); localStorage.setItem(name, value);
} }
}; };
WebUtil.readSetting = function (name, defaultValue) { export function readSetting (name, defaultValue) {
"use strict"; "use strict";
var value; var value;
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
value = WebUtil.settings[name]; value = settings[name];
} else { } else {
value = localStorage.getItem(name); value = localStorage.getItem(name);
} }
@ -168,17 +160,17 @@ WebUtil.readSetting = function (name, defaultValue) {
} }
}; };
WebUtil.eraseSetting = function (name) { export function eraseSetting (name) {
"use strict"; "use strict";
if (window.chrome && window.chrome.storage) { if (window.chrome && window.chrome.storage) {
window.chrome.storage.sync.remove(name); window.chrome.storage.sync.remove(name);
delete WebUtil.settings[name]; delete settings[name];
} else { } else {
localStorage.removeItem(name); 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 // force pretend that we're dealing with a relative path
// (assume that we wanted an extra if we pass one in) // (assume that we wanted an extra if we pass one in)
path = "/" + path; path = "/" + path;
@ -212,7 +204,7 @@ WebUtil.injectParamIfMissing = function (path, param, value) {
// IE11 support or polyfill promises and fetch in IE11. // IE11 support or polyfill promises and fetch in IE11.
// resolve will receive an object on success, while reject // resolve will receive an object on success, while reject
// will receive either an event or an error on failure. // 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 // NB: IE11 doesn't support JSON as a responseType
const req = new XMLHttpRequest(); const req = new XMLHttpRequest();
req.open('GET', path); req.open('GET', path);
@ -240,6 +232,4 @@ WebUtil.fetchJSON = function (path, resolve, reject) {
}; };
req.send(); req.send();
}; }
export default WebUtil;

View File

@ -7,7 +7,7 @@
/*jslint white: false */ /*jslint white: false */
/*global console */ /*global console */
var Base64 = { export default {
/* Convert data (an array of integers) to a Base64 string. */ /* Convert data (an array of integers) to a Base64 string. */
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''), toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=', base64Pad : '=',
@ -15,7 +15,7 @@ var Base64 = {
encode: function (data) { encode: function (data) {
"use strict"; "use strict";
var result = ''; var result = '';
var toBase64Table = Base64.toBase64Table; var toBase64Table = this.toBase64Table;
var length = data.length; var length = data.length;
var lengthpad = (length % 3); var lengthpad = (length % 3);
// Convert every three bytes to 4 ascii characters. // Convert every three bytes to 4 ascii characters.
@ -63,8 +63,8 @@ var Base64 = {
decode: function (data, offset) { decode: function (data, offset) {
"use strict"; "use strict";
offset = typeof(offset) !== 'undefined' ? offset : 0; offset = typeof(offset) !== 'undefined' ? offset : 0;
var toBinaryTable = Base64.toBinaryTable; var toBinaryTable = this.toBinaryTable;
var base64Pad = Base64.base64Pad; var base64Pad = this.base64Pad;
var result, result_length; var result, result_length;
var leftbits = 0; // number of bits decoded, but yet to be appended var leftbits = 0; // number of bits decoded, but yet to be appended
var leftdata = 0; // 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; return result;
} }
}; /* End of Base64 namespace */ }; /* End of Base64 namespace */
export default Base64;

File diff suppressed because it is too large Load Diff

View File

@ -36,4 +36,3 @@ export default function Inflate() {
inflateInit(this.strm, this.windowBits); inflateInit(this.strm, this.windowBits);
}; };

View File

@ -8,395 +8,387 @@
/*jslint browser: true, white: false */ /*jslint browser: true, white: false */
/*global window, Util */ /*global window, Util */
import Util from "../util.js"; import * as Log from '../util/logging.js';
import KeyboardUtil from "./util.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 () { set_defaults(this, defaults, {
"use strict"; 'target': document,
'focused': true
});
// // create the keyboard handler
// Keyboard event handler this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
// KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
KeyboardUtil.TrackKeyState(
Keyboard = function (defaults) { KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
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))
)
) )
); /* jshint newcap: true */ )
); /* jshint newcap: true */
// keep these here so we can refer to them later // keep these here so we can refer to them later
this._eventHandlers = { this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this), 'keyup': this._handleKeyUp.bind(this),
'keydown': this._handleKeyDown.bind(this), 'keydown': this._handleKeyDown.bind(this),
'keypress': this._handleKeyPress.bind(this), 'keypress': this._handleKeyPress.bind(this),
'blur': this._allKeysUp.bind(this) 'blur': this._allKeysUp.bind(this)
};
}; };
};
Keyboard.prototype = { Keyboard.prototype = {
// private methods // private methods
_handleRfbEvent: function (e) { _handleRfbEvent: function (e) {
if (this._onKeyPress) { if (this._onKeyPress) {
Util.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") + Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")"); ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
this._onKeyPress(e); 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);
} }
},
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, [ Mouse.prototype = {
['target', 'wo', 'dom'], // DOM element that captures keyboard input // private methods
['focused', 'rw', 'bool'], // Capture and send key events _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; _releaseMouse: function () {
releaseCapture();
(function () { this._mouseCaptured = false;
Mouse = function (defaults) { },
this._mouseCaptured = false;
_resetDoubleClickTimer: function () {
this._doubleClickTimer = null; this._doubleClickTimer = null;
this._lastTouchPos = null; },
// Configuration attributes _handleMouseButton: function (e, down) {
Util.set_defaults(this, defaults, { if (!this._focused) { return; }
'target': document,
'focused': true,
'touchButton': 1
});
this._eventHandlers = { if (this._notify) {
'mousedown': this._handleMouseDown.bind(this), this._notify(e);
'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);
} }
};
Util.make_properties(Mouse, [ var pos = this._getMousePosition(e);
['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 var bmask;
['onMouseMove', 'rw', 'func'], // Handler for mouse movement if (e.touches || e.changedTouches) {
['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) // 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 };

View File

@ -1,4 +1,4 @@
var KeyTable = { export default {
XK_VoidSymbol: 0xffffff, /* Void symbol */ XK_VoidSymbol: 0xffffff, /* Void symbol */
XK_BackSpace: 0xff08, /* Back space, back char */ XK_BackSpace: 0xff08, /* Back space, back char */
@ -378,5 +378,3 @@ var KeyTable = {
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */ XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */ 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

View File

@ -1,293 +1,277 @@
import KeyTable from "./keysym.js"; import KeyTable from "./keysym.js";
import keysyms from "./keysymdef.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() { function isMac() {
"use strict"; 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) { // Return true if a modifier which is not the specified char modifier (and is not shift) is down
// Any Unicode code points which do not have corresponding keysym entries export function hasShortcutModifier(charModifier, currentModifiers) {
// can be swapped out for another code point by adding them to this table var mods = {};
var substitutions = { for (var key in currentModifiers) {
// {S,s} with comma below -> {S,s} with cedilla if (parseInt(key) !== KeyTable.XK_Shift_L) {
0x218 : 0x15e, mods[key] = currentModifiers[key];
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];
}
} }
}
var sum = 0; var sum = 0;
for (var k in currentModifiers) { for (var k in currentModifiers) {
if (mods[k]) { if (mods[k]) {
++sum; ++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 { else {
return sum > 0; charModifier = [];
} }
} }
// Return true if the specified char modifier is currently down var state = {};
function hasCharModifier(charModifier, currentModifiers) { state[KeyTable.XK_Control_L] = false;
if (charModifier.length === 0) { return 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) { function sync(evt, keysym) {
if (!currentModifiers[charModifier[i]]) { var result = [];
return false; 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 return {
// and generates fake key events to compensate if it gets out of sync // sync on the appropriate keyboard event
function ModifierSync(charModifier) { keydown: function(evt) { return syncKeyEvent(evt, true);},
if (!charModifier) { keyup: function(evt) { return syncKeyEvent(evt, false);},
if (isMac()) { // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
// on Mac, Option (AKA Alt) is used as a char modifier syncAny: function(evt) { return sync(evt);},
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 = [];
}
}
var state = {}; // is a shortcut modifier down?
state[KeyTable.XK_Control_L] = false; hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
state[KeyTable.XK_Alt_L] = false; // if a char modifier is down, return the keys it consists of, otherwise return null
state[KeyTable.XK_ISO_Level3_Shift] = false; activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
state[KeyTable.XK_Shift_L] = false; };
state[KeyTable.XK_Meta_L] = false; }
function sync(evt, keysym) { // Get a key ID from a keyboard event
var result = []; // May be a string or an integer depending on the available properties
function syncKey(keysym) { export function getKey(evt){
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'}; if ('keyCode' in evt && 'key' in evt) {
} return evt.key + ':' + evt.keyCode;
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; }
};
} }
else if ('keyCode' in evt) {
// Get a key ID from a keyboard event return evt.keyCode;
// 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 {
return evt.key;
}
}
// Get the most reliable keysym value we can get from a key event // 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 // if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
function getKeysym(evt){ export function getKeysym(evt){
var codepoint; var codepoint;
if (evt.char && evt.char.length === 1) { if (evt.char && evt.char.length === 1) {
codepoint = evt.char.charCodeAt(); codepoint = evt.char.charCodeAt();
} }
else if (evt.charCode) { else if (evt.charCode) {
codepoint = evt.charCode; codepoint = evt.charCode;
} }
else if (evt.keyCode && evt.type === 'keypress') { else if (evt.keyCode && evt.type === 'keypress') {
// IE10 stores the char code as keyCode, and has no other useful properties // IE10 stores the char code as keyCode, and has no other useful properties
codepoint = evt.keyCode; codepoint = evt.keyCode;
} }
if (codepoint) { if (codepoint) {
return keysyms.fromUnicode(substituteCodepoint(codepoint)); return keysyms.fromUnicode(substituteCodepoint(codepoint));
} }
// we could check evt.key here. // we could check evt.key here.
// Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list, // 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 "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 // so we don't *need* it yet
if (evt.keyCode) { if (evt.keyCode) {
return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey)); return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
} }
if (evt.which) { if (evt.which) {
return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey)); 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; return null;
} }
// won't be accurate for azerty
// Given a keycode, try to predict which keysym it might be. if (keycode >= 0x30 && keycode <= 0x39) {
// If the keycode is unknown, null is returned. return keycode; // digit
function keysymFromKeyCode(keycode, shiftPressed) { }
if (typeof(keycode) !== 'number') { if (keycode >= 0x41 && keycode <= 0x5a) {
return null; // remap to lowercase unless shift is down
} return shiftPressed ? keycode : keycode + 32; // A-Z
// won't be accurate for azerty }
if (keycode >= 0x30 && keycode <= 0x39) { if (keycode >= 0x60 && keycode <= 0x69) {
return keycode; // digit return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
}
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});
} }
// if the key is a known non-character key (any key which doesn't generate character data) switch(keycode) {
// return its keysym value. Otherwise return null case 0x20: return KeyTable.XK_space;
function nonCharacterKey(evt) { case 0x6a: return KeyTable.XK_KP_Multiply;
// evt.key not implemented yet case 0x6b: return KeyTable.XK_KP_Add;
if (!evt.keyCode) { return null; } case 0x6c: return KeyTable.XK_KP_Separator;
var keycode = evt.keyCode; case 0x6d: return KeyTable.XK_KP_Subtract;
case 0x6e: return KeyTable.XK_KP_Decimal;
if (keycode >= 0x70 && keycode <= 0x87) { case 0x6f: return KeyTable.XK_KP_Divide;
return KeyTable.XK_F1 + keycode - 0x70; // F1-F24 case 0xbb: return KeyTable.XK_plus;
} case 0xbc: return KeyTable.XK_comma;
switch (keycode) { case 0xbd: return KeyTable.XK_minus;
case 0xbe: return KeyTable.XK_period;
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;
}
} }
KeyboardUtil.hasShortcutModifier = hasShortcutModifier; return nonCharacterKey({keyCode: keycode});
KeyboardUtil.hasCharModifier = hasCharModifier; }
KeyboardUtil.ModifierSync = ModifierSync;
KeyboardUtil.getKey = getKey;
KeyboardUtil.getKeysym = getKeysym;
KeyboardUtil.keysymFromKeyCode = keysymFromKeyCode;
KeyboardUtil.nonCharacterKey = nonCharacterKey;
KeyboardUtil.substituteCodepoint = substituteCodepoint;
})();
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"; "use strict";
function sendAll(evts) { function sendAll(evts) {
@ -333,7 +317,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
var isShift = evt.keyCode === 0x10 || evt.key === 'Shift'; 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); next(result);
return suppress; return suppress;
@ -357,7 +341,7 @@ KeyboardUtil.QEMUKeyEventDecoder = function(modifierState, next) {
}; };
}; };
KeyboardUtil.TrackQEMUKeyState = function(next) { export function TrackQEMUKeyState (next) {
"use strict"; "use strict";
var state = []; 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" // - 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 // - 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) // 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"; "use strict";
function sendAll(evts) { function sendAll(evts) {
for (var i = 0; i < evts.length; ++i) { for (var i = 0; i < evts.length; ++i) {
@ -434,18 +418,18 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
} }
function process(evt, type) { function process(evt, type) {
var result = {type: type}; var result = {type: type};
var keyId = KeyboardUtil.getKey(evt); var keyId = getKey(evt);
if (keyId) { if (keyId) {
result.keyId = keyId; result.keyId = keyId;
} }
var keysym = KeyboardUtil.getKeysym(evt); var keysym = getKeysym(evt);
var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier(); 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? // 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, // "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 // 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; result.keysym = keysym;
} }
@ -454,11 +438,11 @@ KeyboardUtil.KeyEventDecoder = function(modifierState, next) {
// Should we prevent the browser from handling the event? // Should we prevent the browser from handling the event?
// Doing so on a keydown (in most browsers) prevents keypress from being generated // Doing so on a keydown (in most browsers) prevents keypress from being generated
// so only do that if we have to. // 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, // 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 // 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) // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
var active = modifierState.activeCharModifier(); 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. // 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 // 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 // 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"; "use strict";
var queue = []; var queue = [];
var timer = null; 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 // 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. // 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 // 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"; "use strict";
var state = []; 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 @), // 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. // then the modifier must be "undone" before sending the @, and "redone" afterwards.
KeyboardUtil.EscapeModifiers = function(next) { export function EscapeModifiers (next) {
"use strict"; "use strict";
return function(evt) { return function(evt) {
if (evt.type !== 'keydown' || evt.escape === undefined) { if (evt.type !== 'keydown' || evt.escape === undefined) {
@ -674,5 +658,3 @@ KeyboardUtil.EscapeModifiers = function(next) {
/* jshint shadow: false */ /* jshint shadow: false */
}; };
}; };
export default KeyboardUtil;

View File

@ -1,4 +1,4 @@
var XtScancode = { export default {
"Escape": 0x0001, "Escape": 0x0001,
"Digit1": 0x0002, "Digit1": 0x0002,
"Digit2": 0x0003, "Digit2": 0x0003,
@ -147,5 +147,3 @@ var XtScancode = {
"LaunchMail": 0xE06C, "LaunchMail": 0xE06C,
"MediaSelect": 0xE06D, "MediaSelect": 0xE06D,
}; };
export default XtScancode

File diff suppressed because it is too large Load Diff

View File

@ -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;

120
core/util/browsers.js Normal file
View File

@ -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;
}
}

161
core/util/events.js Normal file
View File

@ -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);
}
};

170
core/util/localization.js Normal file
View File

@ -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);

53
core/util/logging.js Normal file
View File

@ -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();

138
core/util/properties.js Normal file
View File

@ -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]]);
}
}
};

15
core/util/strings.js Normal file
View File

@ -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));
};

View File

@ -12,8 +12,7 @@
* read binary data off of the receive queue. * 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 */ /*jslint browser: true, bitwise: true */
/*global Util*/ /*global Util*/
@ -43,313 +42,310 @@ export default function Websock() {
}; };
}; };
(function () { // this has performance issues in some versions Chromium, and
"use strict"; // doesn't gain a tremendous amount of performance increase in Firefox
// this has performance issues in some versions Chromium, and // at the moment. It may be valuable to turn it on in the future.
// doesn't gain a tremendous amount of performance increase in Firefox var ENABLE_COPYWITHIN = false;
// 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 () { var typedArrayToString = (function () {
// This is only for PhantomJS, which doesn't like apply-ing // This is only for PhantomJS, which doesn't like apply-ing
// with Typed Arrays // with Typed Arrays
try { try {
var arr = new Uint8Array([1, 2, 3]); var arr = new Uint8Array([1, 2, 3]);
String.fromCharCode.apply(null, arr); String.fromCharCode.apply(null, arr);
return function (a) { return String.fromCharCode.apply(null, a); }; return function (a) { return String.fromCharCode.apply(null, a); };
} catch (ex) { } catch (ex) {
return function (a) { return function (a) {
return String.fromCharCode.apply( return String.fromCharCode.apply(
null, Array.prototype.slice.call(a)); 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);
}
}
}
};
})(); })();
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);
}
}
}
};