mirror of
https://github.com/novnc/noVNC.git
synced 2026-06-09 22:04:38 +00:00
Merge branch 'api' of https://github.com/CendioOssman/noVNC
This commit is contained in:
194
core/display.js
194
core/display.js
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
]);
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
|
||||
493
core/rfb.js
493
core/rfb.js
@@ -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);
|
||||
|
||||
@@ -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
40
core/util/eventtarget.js
Normal 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
54
core/util/polyfill.js
Normal 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;
|
||||
}
|
||||
})();
|
||||
@@ -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]]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user