This commit is contained in:
Pierre Ossman
2017-11-10 14:19:05 +01:00
19 changed files with 1675 additions and 1871 deletions

View File

@@ -10,12 +10,10 @@
/*jslint browser: true, white: false */
/*global Util, Base64, changeCursor */
import { browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js';
import { set_defaults, make_properties } from './util/properties.js';
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
export default function Display(defaults) {
export default function Display(target) {
this._drawCtx = null;
this._c_forceCanvas = false;
@@ -32,16 +30,11 @@ export default function Display(defaults) {
this._tile_x = 0;
this._tile_y = 0;
set_defaults(this, defaults, {
'scale': 1.0,
'viewport': false,
'render_mode': '',
"onFlush": function () {},
});
Log.Debug(">> Display.constructor");
// The visible canvas
this._target = target;
if (!this._target) {
throw new Error("Target must be set");
}
@@ -72,23 +65,10 @@ export default function Display(defaults) {
this.clear();
// Check canvas features
if ('createImageData' in this._drawCtx) {
this._render_mode = 'canvas rendering';
} else {
if (!('createImageData' in this._drawCtx)) {
throw new Error("Canvas does not support createImageData");
}
if (this._prefer_js === null) {
Log.Info("Prefering javascript operations");
this._prefer_js = true;
}
// Determine browser support for setting the cursor via data URI scheme
if (this._cursor_uri || this._cursor_uri === null ||
this._cursor_uri === undefined) {
this._cursor_uri = cursorURIsSupported();
}
Log.Debug("<< Display.constructor");
};
@@ -101,13 +81,50 @@ try {
}
Display.prototype = {
// Public methods
// ===== PROPERTIES =====
_scale: 1.0,
get scale() { return this._scale; },
set scale(scale) {
this._rescale(scale);
},
_clipViewport: false,
get clipViewport() { return this._clipViewport; },
set clipViewport(viewport) {
this._clipViewport = viewport;
// May need to readjust the viewport dimensions
var vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
get width() {
return this._fb_width;
},
get height() {
return this._fb_height;
},
get isClipped() {
var vp = this._viewportLoc;
return this._fb_width > vp.w || this._fb_height > vp.h;
},
logo: null,
// ===== EVENT HANDLERS =====
onflush: function () {}, // A flush request has finished
// ===== PUBLIC METHODS =====
viewportChangePos: function (deltaX, deltaY) {
var vp = this._viewportLoc;
deltaX = Math.floor(deltaX);
deltaY = Math.floor(deltaY);
if (!this._viewport) {
if (!this._clipViewport) {
deltaX = -vp.w; // clamped later of out of bounds
deltaY = -vp.h;
}
@@ -146,7 +163,7 @@ Display.prototype = {
viewportChangeSize: function(width, height) {
if (!this._viewport ||
if (!this._clipViewport ||
typeof(width) === "undefined" ||
typeof(height) === "undefined") {
@@ -307,7 +324,7 @@ Display.prototype = {
flush: function() {
if (this._renderQ.length === 0) {
this._onFlush();
this.onflush();
} else {
this._flushing = true;
}
@@ -382,56 +399,45 @@ Display.prototype = {
this._tile = this._drawCtx.createImageData(width, height);
}
if (this._prefer_js) {
var red = color[2];
var green = color[1];
var blue = color[0];
var red = color[2];
var green = color[1];
var blue = color[0];
var data = this._tile.data;
for (var i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
} else {
this.fillRect(x, y, width, height, color, true);
var data = this._tile.data;
for (var i = 0; i < width * height * 4; i += 4) {
data[i] = red;
data[i + 1] = green;
data[i + 2] = blue;
data[i + 3] = 255;
}
},
// update sub-rectangle of the current tile
subTile: function (x, y, w, h, color) {
if (this._prefer_js) {
var red = color[2];
var green = color[1];
var blue = color[0];
var xend = x + w;
var yend = y + h;
var red = color[2];
var green = color[1];
var blue = color[0];
var xend = x + w;
var yend = y + h;
var data = this._tile.data;
var width = this._tile.width;
for (var j = y; j < yend; j++) {
for (var i = x; i < xend; i++) {
var p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
var data = this._tile.data;
var width = this._tile.width;
for (var j = y; j < yend; j++) {
for (var i = x; i < xend; i++) {
var p = (i + (j * width)) * 4;
data[p] = red;
data[p + 1] = green;
data[p + 2] = blue;
data[p + 3] = 255;
}
} else {
this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true);
}
},
// draw the current tile to the screen
finishTile: function () {
if (this._prefer_js) {
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
this._damage(this._tile_x, this._tile_y,
this._tile.width, this._tile.height);
}
// else: No-op -- already done by setSubTile
this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y);
this._damage(this._tile_x, this._tile_y,
this._tile.width, this._tile.height);
},
blitImage: function (x, y, width, height, arr, offset, from_queue) {
@@ -500,11 +506,6 @@ Display.prototype = {
},
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
if (this._cursor_uri === false) {
Log.Warn("changeCursor called but no cursor data URI support");
return;
}
Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h);
},
@@ -516,32 +517,7 @@ Display.prototype = {
this._target.style.cursor = "none";
},
clippingDisplay: function () {
var vp = this._viewportLoc;
return this._fb_width > vp.w || this._fb_height > vp.h;
},
// Overridden getters/setters
set_scale: function (scale) {
this._rescale(scale);
},
set_viewport: function (viewport) {
this._viewport = viewport;
// May need to readjust the viewport dimensions
var vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
},
get_width: function () {
return this._fb_width;
},
get_height: function () {
return this._fb_height;
},
autoscale: function (containerWidth, containerHeight, downscaleOnly) {
autoscale: function (containerWidth, containerHeight) {
var vp = this._viewportLoc;
var targetAspectRatio = containerWidth / containerHeight;
var fbAspectRatio = vp.w / vp.h;
@@ -553,14 +529,11 @@ Display.prototype = {
scaleRatio = containerHeight / vp.h;
}
if (scaleRatio > 1.0 && downscaleOnly) {
scaleRatio = 1.0;
}
this._rescale(scaleRatio);
},
// Private Methods
// ===== PRIVATE METHODS =====
_rescale: function (factor) {
this._scale = factor;
var vp = this._viewportLoc;
@@ -685,28 +658,11 @@ Display.prototype = {
if (this._renderQ.length === 0 && this._flushing) {
this._flushing = false;
this._onFlush();
this.onflush();
}
},
};
make_properties(Display, [
['target', 'wo', 'dom'], // Canvas element for rendering
['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only)
['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data}
['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
['viewport', 'rw', 'bool'], // Use viewport clipping
['width', 'ro', 'int'], // Display area width
['height', 'ro', 'int'], // Display area height
['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods
['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI
['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished
]);
// Class Methods
Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) {
if ((w === 0) || (h === 0)) {

View File

@@ -10,7 +10,6 @@
import * as Log from '../util/logging.js';
import { stopEvent } from '../util/events.js';
import { set_defaults, make_properties } from '../util/properties.js';
import * as KeyboardUtil from "./util.js";
import KeyTable from "./keysym.js";
@@ -18,15 +17,13 @@ import KeyTable from "./keysym.js";
// Keyboard event handler
//
export default function Keyboard(defaults) {
export default function Keyboard(target) {
this._target = target || null;
this._keyDownList = {}; // List of depressed keys
// (even if they are happy)
this._pendingKey = null; // Key waiting for keypress
set_defaults(this, defaults, {
'target': null,
});
// keep these here so we can refer to them later
this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this),
@@ -56,14 +53,14 @@ function isEdge() {
}
Keyboard.prototype = {
// private methods
// ===== EVENT HANDLERS =====
onkeyevent: function () {}, // Handler for key press/release
// ===== PRIVATE METHODS =====
_sendKeyEvent: function (keysym, code, down) {
if (!this._onKeyEvent) {
return;
}
Log.Debug("onKeyEvent " + (down ? "down" : "up") +
Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code);
// Windows sends CtrlLeft+AltRight when you press
@@ -77,19 +74,19 @@ Keyboard.prototype = {
('ControlLeft' in this._keyDownList) &&
('AltRight' in this._keyDownList)) {
fakeAltGraph = true;
this._onKeyEvent(this._keyDownList['AltRight'],
this.onkeyevent(this._keyDownList['AltRight'],
'AltRight', false);
this._onKeyEvent(this._keyDownList['ControlLeft'],
this.onkeyevent(this._keyDownList['ControlLeft'],
'ControlLeft', false);
}
}
this._onKeyEvent(keysym, code, down);
this.onkeyevent(keysym, code, down);
if (fakeAltGraph) {
this._onKeyEvent(this._keyDownList['ControlLeft'],
this.onkeyevent(this._keyDownList['ControlLeft'],
'ControlLeft', true);
this._onKeyEvent(this._keyDownList['AltRight'],
this.onkeyevent(this._keyDownList['AltRight'],
'AltRight', true);
}
},
@@ -305,7 +302,7 @@ Keyboard.prototype = {
Log.Debug("<< Keyboard.allKeysUp");
},
// Public methods
// ===== PUBLIC METHODS =====
grab: function () {
//Log.Debug(">> Keyboard.grab");
@@ -336,9 +333,3 @@ Keyboard.prototype = {
//Log.Debug(">> Keyboard.ungrab");
},
};
make_properties(Keyboard, [
['target', 'wo', 'dom'], // DOM element that captures keyboard input
['onKeyEvent', 'rw', 'func'] // Handler for key press/release
]);

View File

@@ -11,13 +11,13 @@
import * as Log from '../util/logging.js';
import { isTouchDevice } from '../util/browsers.js';
import { setCapture, stopEvent, getPointerEvent } from '../util/events.js';
import { set_defaults, make_properties } from '../util/properties.js';
var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step
var WHEEL_STEP_TIMEOUT = 50; // ms
var WHEEL_LINE_HEIGHT = 19;
export default function Mouse(defaults) {
export default function Mouse(target) {
this._target = target || document;
this._doubleClickTimer = null;
this._lastTouchPos = null;
@@ -28,12 +28,6 @@ export default function Mouse(defaults) {
this._accumulatedWheelDeltaX = 0;
this._accumulatedWheelDeltaY = 0;
// Configuration attributes
set_defaults(this, defaults, {
'target': document,
'touchButton': 1
});
this._eventHandlers = {
'mousedown': this._handleMouseDown.bind(this),
'mouseup': this._handleMouseUp.bind(this),
@@ -44,7 +38,16 @@ export default function Mouse(defaults) {
};
Mouse.prototype = {
// private methods
// ===== PROPERTIES =====
touchButton: 1, // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
// ===== EVENT HANDLERS =====
onmousebutton: function () {}, // Handler for mouse button click/release
onmousemove: function () {}, // Handler for mouse movement
// ===== PRIVATE METHODS =====
_resetDoubleClickTimer: function () {
this._doubleClickTimer = null;
@@ -83,7 +86,7 @@ Mouse.prototype = {
}
this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500);
}
bmask = this._touchButton;
bmask = this.touchButton;
// If bmask is set
} else if (e.which) {
/* everything except IE */
@@ -95,11 +98,10 @@ Mouse.prototype = {
(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);
}
Log.Debug("onmousebutton " + (down ? "down" : "up") +
", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask);
this.onmousebutton(pos.x, pos.y, down, bmask);
stopEvent(e);
},
@@ -122,11 +124,11 @@ Mouse.prototype = {
_generateWheelStepX: function () {
if (this._accumulatedWheelDeltaX < 0) {
this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 5);
this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 5);
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5);
} else if (this._accumulatedWheelDeltaX > 0) {
this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 6);
this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 6);
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6);
}
this._accumulatedWheelDeltaX = 0;
@@ -135,11 +137,11 @@ Mouse.prototype = {
_generateWheelStepY: function () {
if (this._accumulatedWheelDeltaY < 0) {
this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 3);
this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 3);
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3);
} else if (this._accumulatedWheelDeltaY > 0) {
this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 4);
this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 4);
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4);
this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4);
}
this._accumulatedWheelDeltaY = 0;
@@ -153,8 +155,6 @@ Mouse.prototype = {
},
_handleMouseWheel: function (e) {
if (!this._onMouseButton) { return; }
this._resetWheelStepTimers();
this._updateMousePosition(e);
@@ -199,9 +199,7 @@ Mouse.prototype = {
_handleMouseMove: function (e) {
this._updateMousePosition(e);
if (this._onMouseMove) {
this._onMouseMove(this._pos.x, this._pos.y);
}
this.onmousemove(this._pos.x, this._pos.y);
stopEvent(e);
},
@@ -240,7 +238,8 @@ Mouse.prototype = {
this._pos = {x:x, y:y};
},
// Public methods
// ===== PUBLIC METHODS =====
grab: function () {
var c = this._target;
@@ -282,11 +281,3 @@ Mouse.prototype = {
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
}
};
make_properties(Mouse, [
['target', 'ro', 'dom'], // DOM element that captures mouse input
['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)
]);

View File

@@ -13,7 +13,8 @@
import * as Log from './util/logging.js';
import _ from './util/localization.js';
import { decodeUTF8 } from './util/strings.js';
import { set_defaults, make_properties } from './util/properties.js';
import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js';
import EventTargetMixin from './util/eventtarget.js';
import Display from "./display.js";
import Keyboard from "./input/keyboard.js";
import Mouse from "./input/mouse.js";
@@ -24,50 +25,78 @@ import KeyTable from "./input/keysym.js";
import XtScancode from "./input/xtscancodes.js";
import Inflator from "./inflator.js";
import { encodings, encodingName } from "./encodings.js";
import "./util/polyfill.js";
/*jslint white: false, browser: true */
/*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */
export default function RFB(defaults) {
"use strict";
if (!defaults) {
defaults = {};
// How many seconds to wait for a disconnect to finish
var DISCONNECT_TIMEOUT = 3;
export default function RFB(target, url, options) {
if (!target) {
throw Error("Must specify target");
}
if (!url) {
throw Error("Must specify URL");
}
this._rfb_host = '';
this._rfb_port = 5900;
this._rfb_password = '';
this._rfb_path = '';
this._target = target;
this._url = url;
// Connection details
options = options || {}
this._rfb_credentials = options.credentials || {};
this._shared = 'shared' in options ? !!options.shared : true;
this._repeaterID = options.repeaterID || '';
// Internal state
this._rfb_connection_state = '';
this._rfb_init_state = '';
this._rfb_version = 0;
this._rfb_max_version = 3.8;
this._rfb_auth_scheme = '';
this._rfb_disconnect_reason = "";
// Server capabilities
this._rfb_version = 0;
this._rfb_max_version = 3.8;
this._rfb_tightvnc = false;
this._rfb_xvp_ver = 0;
this._encHandlers = {};
this._encStats = {};
this._fb_width = 0;
this._fb_height = 0;
this._sock = null; // Websock object
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._mouse = null; // Mouse input handler object
this._disconnTimer = null; // disconnection timer
this._fb_name = "";
this._capabilities = { power: false, resize: false };
this._supportsFence = false;
this._supportsContinuousUpdates = false;
this._enabledContinuousUpdates = false;
// Frame buffer update state
this._supportsSetDesktopSize = false;
this._screen_id = 0;
this._screen_flags = 0;
this._qemuExtKeyEventSupported = false;
// Internal objects
this._sock = null; // Websock object
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._mouse = null; // Mouse input handler object
// Timers
this._disconnTimer = null; // disconnection timer
// Decoder states and stats
this._encHandlers = {};
this._encStats = {};
this._FBU = {
rects: 0,
subrects: 0, // RRE
subrects: 0, // RRE and HEXTILE
lines: 0, // RAW
tiles: 0, // HEXTILE
bytes: 0,
@@ -78,12 +107,11 @@ export default function RFB(defaults) {
encoding: 0,
subencoding: -1,
background: null,
zlib: [] // TIGHT zlib streams
zlibs: [] // TIGHT zlib streams
};
this._fb_width = 0;
this._fb_height = 0;
this._fb_name = "";
for (var i = 0; i < 4; i++) {
this._FBU.zlibs[i] = new Inflator();
}
this._destBuff = null;
this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
@@ -103,10 +131,6 @@ export default function RFB(defaults) {
pixels: 0
};
this._supportsSetDesktopSize = false;
this._screen_id = 0;
this._screen_flags = 0;
// Mouse state
this._mouse_buttonMask = 0;
this._mouse_arr = [];
@@ -114,37 +138,6 @@ export default function RFB(defaults) {
this._viewportDragPos = {};
this._viewportHasMoved = false;
// QEMU Extended Key Event support - default to false
this._qemuExtKeyEventSupported = false;
// set the default value on user-facing properties
set_defaults(this, defaults, {
'target': 'null', // VNC display rendering Canvas object
'encrypt': false, // Use TLS/SSL/wss encryption
'local_cursor': false, // Request locally rendered cursor
'shared': true, // Request shared mode
'view_only': false, // Disable client mouse/keyboard
'focus_on_click': true, // Grab focus on canvas on mouse click
'xvp_password_sep': '@', // Separator for XVP password fields
'disconnectTimeout': 3, // Time (s) to wait for disconnection
'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection
'repeaterID': '', // [UltraVNC] RepeaterID to connect to
'viewportDrag': false, // Move the viewport on mouse drags
// Callback functions
'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change
'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI
'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished
'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required
'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received
'onBell': function () { }, // onBell(rfb): RFB Bell message received
'onFBUReceive': function () { }, // onFBUReceive(rfb, rect): RFB FBU rect received but not yet processed
'onFBUComplete': function () { }, // onFBUComplete(rfb): RFB FBU received and processed
'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized
'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received
'onXvpInit': function () { } // onXvpInit(version): XVP extensions active for this connection
});
// Bound event handlers
this._eventHandlers = {
focusCanvas: this._focusCanvas.bind(this),
@@ -174,19 +167,20 @@ export default function RFB(defaults) {
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
try {
this._display = new Display({target: this._target,
onFlush: this._onFlush.bind(this)});
this._display = new Display(this._target);
} catch (exc) {
Log.Error("Display exception: " + exc);
throw exc;
}
this._display.onflush = this._onFlush.bind(this);
this._display.clear();
this._keyboard = new Keyboard({target: this._target,
onKeyEvent: this._handleKeyEvent.bind(this)});
this._keyboard = new Keyboard(this._target);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
this._mouse = new Mouse({target: this._target,
onMouseButton: this._handleMouseButton.bind(this),
onMouseMove: this._handleMouseMove.bind(this)});
this._mouse = new Mouse(this._target);
this._mouse.onmousebutton = this._handleMouseButton.bind(this);
this._mouse.onmousemove = this._handleMouseMove.bind(this);
this._sock = new Websock();
this._sock.on('message', this._handle_message.bind(this));
@@ -236,33 +230,51 @@ export default function RFB(defaults) {
Log.Warn("WebSocket on-error event");
});
this._init_vars();
this._cleanup();
var rmode = this._display.get_render_mode();
Log.Info("Using native WebSockets, render mode: " + rmode);
// Slight delay of the actual connection so that the caller has
// time to set up callbacks
setTimeout(this._updateConnectionState.bind(this, 'connecting'));
Log.Debug("<< RFB.constructor");
};
RFB.prototype = {
// Public methods
connect: function (host, port, password, path) {
this._rfb_host = host;
this._rfb_port = port;
this._rfb_password = (password !== undefined) ? password : "";
this._rfb_path = (path !== undefined) ? path : "";
// ===== PROPERTIES =====
if (!this._rfb_host) {
return this._fail(
_("Must set host"));
dragViewport: false,
focusOnClick: true,
_viewOnly: false,
get viewOnly() { return this._viewOnly; },
set viewOnly(viewOnly) {
this._viewOnly = viewOnly;
if (this._rfb_connection_state === "connecting" ||
this._rfb_connection_state === "connected") {
if (viewOnly) {
this._keyboard.ungrab();
this._mouse.ungrab();
} else {
this._keyboard.grab();
this._mouse.grab();
}
}
this._rfb_init_state = '';
this._updateConnectionState('connecting');
return true;
},
get capabilities() { return this._capabilities; },
get touchButton() { return this._mouse.touchButton; },
set touchButton(button) { this._mouse.touchButton = button; },
get viewportScale() { return this._display.scale; },
set viewportScale(scale) { this._display.scale = scale; },
get clipViewport() { return this._display.clipViewport; },
set clipViewport(viewport) { this._display.clipViewport = viewport; },
get isClipped() { return this._display.isClipped; },
// ===== PUBLIC METHODS =====
disconnect: function () {
this._updateConnectionState('disconnecting');
this._sock.off('error');
@@ -270,13 +282,13 @@ RFB.prototype = {
this._sock.off('open');
},
sendPassword: function (passwd) {
this._rfb_password = passwd;
sendCredentials: function (creds) {
this._rfb_credentials = creds;
setTimeout(this._init_msg.bind(this), 0);
},
sendCtrlAltDel: function () {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
Log.Info("Sending Ctrl-Alt-Del");
this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
@@ -285,38 +297,29 @@ RFB.prototype = {
this.sendKey(KeyTable.XK_Delete, "Delete", false);
this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
return true;
},
xvpOp: function (ver, op) {
if (this._rfb_xvp_ver < ver) { return false; }
Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op));
return true;
machineShutdown: function () {
this._xvpOp(1, 2);
},
xvpShutdown: function () {
return this.xvpOp(1, 2);
machineReboot: function () {
this._xvpOp(1, 3);
},
xvpReboot: function () {
return this.xvpOp(1, 3);
},
xvpReset: function () {
return this.xvpOp(1, 4);
machineReset: function () {
this._xvpOp(1, 4);
},
// Send a key press. If 'down' is not specified then send a down key
// followed by an up key.
sendKey: function (keysym, code, down) {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
if (down === undefined) {
this.sendKey(keysym, code, true);
this.sendKey(keysym, code, false);
return true;
return;
}
var scancode = XtScancode[code];
@@ -330,63 +333,55 @@ RFB.prototype = {
RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
} else {
if (!keysym) {
return false;
return;
}
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
return true;
},
clipboardPasteFrom: function (text) {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return; }
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
RFB.messages.clientCutText(this._sock, text);
},
autoscale: function (width, height) {
if (this._rfb_connection_state !== 'connected') { return; }
this._display.autoscale(width, height);
},
viewportChangeSize: function(width, height) {
if (this._rfb_connection_state !== 'connected') { return; }
this._display.viewportChangeSize(width, height);
},
// Requests a change of remote desktop size. This message is an extension
// and may only be sent if we have received an ExtendedDesktopSize message
requestDesktopSize: function (width, height) {
if (this._rfb_connection_state !== 'connected' ||
this._view_only) {
return false;
this._viewOnly) {
return;
}
if (this._supportsSetDesktopSize) {
RFB.messages.setDesktopSize(this._sock, width, height,
this._screen_id, this._screen_flags);
this._sock.flush();
return true;
} else {
return false;
if (!this._supportsSetDesktopSize) {
return;
}
RFB.messages.setDesktopSize(this._sock, width, height,
this._screen_id, this._screen_flags);
},
// Private methods
// ===== PRIVATE METHODS =====
_connect: function () {
Log.Debug(">> RFB.connect");
this._init_vars();
var uri;
if (typeof UsingSocketIO !== 'undefined') {
uri = 'http';
} else {
uri = this._encrypt ? 'wss' : 'ws';
}
uri += '://' + this._rfb_host;
if(this._rfb_port) {
uri += ':' + this._rfb_port;
}
uri += '/' + this._rfb_path;
Log.Info("connecting to " + uri);
Log.Info("connecting to " + this._url);
try {
// WebSocket.onopen transitions to the RFB init states
this._sock.open(uri, this._wsProtocols);
this._sock.open(this._url, ['binary']);
} catch (e) {
if (e.name === 'SyntaxError') {
this._fail("Invalid host or port value given", e);
@@ -412,29 +407,6 @@ RFB.prototype = {
Log.Debug("<< RFB.disconnect");
},
_init_vars: function () {
// reset state
this._FBU.rects = 0;
this._FBU.subrects = 0; // RRE and HEXTILE
this._FBU.lines = 0; // RAW
this._FBU.tiles = 0; // HEXTILE
this._FBU.zlibs = []; // TIGHT zlib encoders
this._mouse_buttonMask = 0;
this._mouse_arr = [];
this._rfb_tightvnc = false;
// Clear the per connection encoding stats
var stats = this._encStats;
Object.keys(stats).forEach(function (key) {
stats[key][0] = 0;
});
var i;
for (i = 0; i < 4; i++) {
this._FBU.zlibs[i] = new Inflator();
}
},
_print_stats: function () {
var stats = this._encStats;
@@ -454,11 +426,11 @@ RFB.prototype = {
},
_cleanup: function () {
if (!this._view_only) { this._keyboard.ungrab(); }
if (!this._view_only) { this._mouse.ungrab(); }
if (!this._viewOnly) { this._keyboard.ungrab(); }
if (!this._viewOnly) { this._mouse.ungrab(); }
this._display.defaultCursor();
if (Log.get_logging() !== 'debug') {
// Show noVNC logo on load and when disconnected, unless in
// Show noVNC logo when disconnected, unless in
// debug mode
this._display.clear();
}
@@ -540,7 +512,8 @@ RFB.prototype = {
// State change actions
this._rfb_connection_state = state;
this._onUpdateState(this, state, oldstate);
var event = new CustomEvent("updatestate", { detail: { state: state } });
this.dispatchEvent(event);
var smsg = "New state '" + state + "', was '" + oldstate + "'.";
Log.Debug(smsg);
@@ -556,14 +529,16 @@ RFB.prototype = {
switch (state) {
case 'disconnected':
// Call onDisconnected callback after onUpdateState since
// Fire disconnected event after updatestate event since
// we don't know if the UI only displays the latest message
if (this._rfb_disconnect_reason !== "") {
this._onDisconnected(this, this._rfb_disconnect_reason);
event = new CustomEvent("disconnect",
{ detail: { reason: this._rfb_disconnect_reason } });
} else {
// No reason means clean disconnect
this._onDisconnected(this);
event = new CustomEvent("disconnect", { detail: {} });
}
this.dispatchEvent(event);
break;
case 'connecting':
@@ -576,7 +551,7 @@ RFB.prototype = {
this._disconnTimer = setTimeout(function () {
this._rfb_disconnect_reason = _("Disconnect timeout");
this._updateConnectionState('disconnected');
}.bind(this), this._disconnectTimeout * 1000);
}.bind(this), DISCONNECT_TIMEOUT * 1000);
break;
}
},
@@ -618,11 +593,10 @@ RFB.prototype = {
* Send a notification to the UI. Valid levels are:
* 'normal'|'warn'|'error'
*
* NOTE: Options could be added in the future.
* NOTE: If this function is called multiple times, remember that the
* interface could be only showing the latest notification.
*/
_notification: function(msg, level, options) {
_notification: function(msg, level) {
switch (level) {
case 'normal':
case 'warn':
@@ -634,11 +608,16 @@ RFB.prototype = {
return;
}
if (options) {
this._onNotification(this, msg, level, options);
} else {
this._onNotification(this, msg, level);
}
var event = new CustomEvent("notification",
{ detail: { message: msg, level: level } });
this.dispatchEvent(event);
},
_setCapability: function (cap, val) {
this._capabilities[cap] = val;
var event = new CustomEvent("capabilities",
{ detail: { capabilities: this._capabilities } });
this.dispatchEvent(event);
},
_handle_message: function () {
@@ -681,7 +660,7 @@ RFB.prototype = {
this._mouse_buttonMask &= ~bmask;
}
if (this._viewportDrag) {
if (this.dragViewport) {
if (down && !this._viewportDragging) {
this._viewportDragging = true;
this._viewportDragPos = {'x': x, 'y': y};
@@ -693,14 +672,14 @@ RFB.prototype = {
// If the viewport didn't actually move, then treat as a mouse click event
// Send the button down event here, as the button up event is sent at the end of this function
if (!this._viewportHasMoved && !this._view_only) {
if (!this._viewportHasMoved && !this._viewOnly) {
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
}
this._viewportHasMoved = false;
}
}
if (this._view_only) { return; } // View only, skip mouse events
if (this._viewOnly) { return; } // View only, skip mouse events
if (this._rfb_connection_state !== 'connected') { return; }
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
@@ -727,7 +706,7 @@ RFB.prototype = {
return;
}
if (this._view_only) { return; } // View only, skip mouse events
if (this._viewOnly) { return; } // View only, skip mouse events
if (this._rfb_connection_state !== 'connected') { return; }
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
@@ -768,7 +747,7 @@ RFB.prototype = {
}
if (is_repeater) {
var repeaterID = this._repeaterID;
var repeaterID = "ID:" + this._repeaterID;
while (repeaterID.length < 250) {
repeaterID += "\0";
}
@@ -845,21 +824,20 @@ RFB.prototype = {
// authentication
_negotiate_xvp_auth: function () {
var xvp_sep = this._xvp_password_sep;
var xvp_auth = this._rfb_password.split(xvp_sep);
if (xvp_auth.length < 3) {
var msg = 'XVP credentials required (user' + xvp_sep +
'target' + xvp_sep + 'password) -- got only ' + this._rfb_password;
this._onPasswordRequired(this, msg);
if (!this._rfb_credentials.username ||
!this._rfb_credentials.password ||
!this._rfb_credentials.target) {
var event = new CustomEvent("credentialsrequired",
{ detail: { types: ["username", "password", "target"] } });
this.dispatchEvent(event);
return false;
}
var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) +
String.fromCharCode(xvp_auth[1].length) +
xvp_auth[0] +
xvp_auth[1];
var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
String.fromCharCode(this._rfb_credentials.target.length) +
this._rfb_credentials.username +
this._rfb_credentials.target;
this._sock.send_string(xvp_auth_str);
this._rfb_password = xvp_auth.slice(2).join(xvp_sep);
this._rfb_auth_scheme = 2;
return this._negotiate_authentication();
},
@@ -867,14 +845,16 @@ RFB.prototype = {
_negotiate_std_vnc_auth: function () {
if (this._sock.rQwait("auth challenge", 16)) { return false; }
if (this._rfb_password.length === 0) {
this._onPasswordRequired(this);
if (!this._rfb_credentials.password) {
var event = new CustomEvent("credentialsrequired",
{ detail: { types: ["password"] } });
this.dispatchEvent(event);
return false;
}
// TODO(directxman12): make genDES not require an Array
var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16));
var response = RFB.genDES(this._rfb_password, challenge);
var response = RFB.genDES(this._rfb_credentials.password, challenge);
this._sock.send(response);
this._rfb_init_state = "SecurityResult";
return true;
@@ -1105,12 +1085,14 @@ RFB.prototype = {
}
// we're past the point where we could backtrack, so it's safe to call this
this._onDesktopName(this, this._fb_name);
var event = new CustomEvent("desktopname",
{ detail: { name: this._fb_name } });
this.dispatchEvent(event);
this._resize(width, height);
if (!this._view_only) { this._keyboard.grab(); }
if (!this._view_only) { this._mouse.grab(); }
if (!this._viewOnly) { this._keyboard.grab(); }
if (!this._viewOnly) { this._mouse.grab(); }
this._fb_depth = 24;
@@ -1160,7 +1142,8 @@ RFB.prototype = {
encs.push(encodings.pseudoEncodingFence);
encs.push(encodings.pseudoEncodingContinuousUpdates);
if (this._local_cursor && this._fb_depth == 24) {
if (browserSupportsCursorURIs() &&
!isTouchDevice && this._fb_depth == 24) {
encs.push(encodings.pseudoEncodingCursor);
}
@@ -1219,9 +1202,11 @@ RFB.prototype = {
var text = this._sock.rQshiftStr(length);
if (this._view_only) { return true; }
if (this._viewOnly) { return true; }
this._onClipboard(this, text);
var event = new CustomEvent("clipboard",
{ detail: { text: text } });
this.dispatchEvent(event);
return true;
},
@@ -1283,7 +1268,7 @@ RFB.prototype = {
case 1: // XVP_INIT
this._rfb_xvp_ver = xvp_ver;
Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")");
this._onXvpInit(this._rfb_xvp_ver);
this._setCapability("power", true);
break;
default:
this._fail("Unexpected server message",
@@ -1317,7 +1302,8 @@ RFB.prototype = {
case 2: // Bell
Log.Debug("Bell");
this._onBell(this);
var event = new CustomEvent("bell", { detail: {} });
this.dispatchEvent(event);
return true;
case 3: // ServerCutText
@@ -1398,12 +1384,6 @@ RFB.prototype = {
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
(hdr[10] << 8) + hdr[11], 10);
this._onFBUReceive(this,
{'x': this._FBU.x, 'y': this._FBU.y,
'width': this._FBU.width, 'height': this._FBU.height,
'encoding': this._FBU.encoding,
'encodingName': encodingName(this._FBU.encoding)});
if (!this._encHandlers[this._FBU.encoding]) {
this._fail("Unexpected server message",
"Unsupported encoding " +
@@ -1458,8 +1438,6 @@ RFB.prototype = {
this._display.flip();
this._onFBUComplete(this);
return true; // We finished this FBU
},
@@ -1477,77 +1455,24 @@ RFB.prototype = {
this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4);
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
var event = new CustomEvent("fbresize",
{ detail: { width: this._fb_width,
height: this._fb_height } });
this.dispatchEvent(event);
this._timing.fbu_rt_start = (new Date()).getTime();
this._updateContinuousUpdates();
}
},
_xvpOp: function (ver, op) {
if (this._rfb_xvp_ver < ver) { return; }
Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
RFB.messages.xvpOp(this._sock, ver, op);
},
};
make_properties(RFB, [
['target', 'wo', 'dom'], // VNC display rendering Canvas object
['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption
['local_cursor', 'rw', 'bool'], // Request locally rendered cursor
['shared', 'rw', 'bool'], // Request shared mode
['view_only', 'rw', 'bool'], // Disable client mouse/keyboard
['focus_on_click', 'rw', 'bool'], // Grab focus on canvas on mouse click
['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields
['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection
['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection
['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to
['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags
// Callback functions
['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change
['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI
['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished
['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required
['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received
['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received
['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed
['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed
['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized
['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received
['onXvpInit', 'rw', 'func'] // onXvpInit(version): XVP extensions active for this connection
]);
RFB.prototype.set_local_cursor = function (cursor) {
if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) {
this._local_cursor = false;
this._display.disableLocalCursor(); //Only show server-side cursor
} else {
if (this._display.get_cursor_uri()) {
this._local_cursor = true;
} else {
Log.Warn("Browser does not support local cursor");
this._display.disableLocalCursor();
}
}
// Need to send an updated list of encodings if we are connected
if (this._rfb_connection_state === "connected") {
this._sendEncodings();
}
};
RFB.prototype.set_view_only = function (view_only) {
this._view_only = view_only;
if (this._rfb_connection_state === "connecting" ||
this._rfb_connection_state === "connected") {
if (view_only) {
this._keyboard.ungrab();
this._mouse.ungrab();
} else {
this._keyboard.grab();
this._mouse.grab();
}
}
};
RFB.prototype.get_display = function () { return this._display; };
RFB.prototype.get_keyboard = function () { return this._keyboard; };
RFB.prototype.get_mouse = function () { return this._mouse; };
Object.assign(RFB.prototype, EventTargetMixin);
// Class Methods
RFB.messages = {
@@ -1830,7 +1755,21 @@ RFB.messages = {
sock._sQlen += 10;
sock.flush();
}
},
xvpOp: function (sock, ver, op) {
var buff = sock._sQ;
var offset = sock._sQlen;
buff[offset] = 250; // msg-type
buff[offset + 1] = 0; // padding
buff[offset + 2] = ver;
buff[offset + 3] = op;
sock._sQlen += 4;
sock.flush();
},
};
RFB.genDES = function (password, challenge) {
@@ -2361,6 +2300,8 @@ RFB.encodingHandlers = {
if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._supportsSetDesktopSize = true;
this._setCapability("resize", true);
var number_of_screens = this._sock.rQpeek8();
this._FBU.bytes = 4 + (number_of_screens * 16);

View File

@@ -43,11 +43,3 @@ export function browserSupportsCursorURIs () {
return _cursor_uris_supported;
};
export function _forceCursorURIs(enabled) {
if (enabled === undefined || enabled) {
_cursor_uris_supported = true;
} else {
_cursor_uris_supported = false;
}
}

40
core/util/eventtarget.js Normal file
View File

@@ -0,0 +1,40 @@
/*
* noVNC: HTML5 VNC client
* Copyright 2017 Pierre Ossman for Cendio AB
* Licensed under MPL 2.0 (see LICENSE.txt)
*
* See README.md for usage and integration instructions.
*/
var EventTargetMixin = {
_listeners: null,
addEventListener: function(type, callback) {
if (!this._listeners) {
this._listeners = new Map();
}
if (!this._listeners.has(type)) {
this._listeners.set(type, new Set());
}
this._listeners.get(type).add(callback);
},
removeEventListener: function(type, callback) {
if (!this._listeners || !this._listeners.has(type)) {
return;
}
this._listeners.get(type).delete(callback);
},
dispatchEvent: function(event) {
if (!this._listeners || !this._listeners.has(event.type)) {
return true;
}
this._listeners.get(event.type).forEach(function (callback) {
callback.call(this, event);
}, this);
return !event.defaultPrevented;
},
};
export default EventTargetMixin;

54
core/util/polyfill.js Normal file
View File

@@ -0,0 +1,54 @@
/*
* noVNC: HTML5 VNC client
* Copyright 2017 Pierre Ossman for noVNC
* Licensed under MPL 2.0 or any later version (see LICENSE.txt)
*/
/* Polyfills to provide new APIs in old browsers */
/* Object.assign() (taken from MDN) */
if (typeof Object.assign != 'function') {
// Must be writable: true, enumerable: false, configurable: true
Object.defineProperty(Object, "assign", {
value: function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
},
writable: true,
configurable: true
});
}
/* CustomEvent constructor (taken from MDN) */
(function () {
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
if (typeof window.CustomEvent !== "function") {
window.CustomEvent = CustomEvent;
}
})();

View File

@@ -1,138 +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.
*/
/*
* 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]]);
}
}
};