Always use the shorthand notation if the function is a method of an object or class `{ foo() { ... } }` or `class bar { foo() { ... } }`
unless it's a callback in which case you a fat arrow function should be used `{ cb: () => { ... } }`
{
- "env": {
- "browser": true,
- "es6": true
- },
- "parserOptions": {
- "sourceType": "module"
- },
- "extends": "eslint:recommended",
- "rules": {
- "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
- "no-constant-condition": ["error", { "checkLoops": false }],
- "no-var": "error"
- }
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "extends": "eslint:recommended",
+ "rules": {
+ "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }],
+ "no-constant-condition": ["error", { "checkLoops": false }],
+ "no-var": "error",
+ "no-useless-constructor": "error",
+ "object-shorthand": ["error", "methods", { "avoidQuotes": true }],
+ }
}
* Localization Utilities
*/
-export function Localizer() {
- // Currently configured language
- this.language = 'en';
+export class Localizer {
+ constructor() {
+ // Currently configured language
+ this.language = 'en';
- // Current dictionary of translations
- this.dictionary = undefined;
-}
+ // Current dictionary of translations
+ this.dictionary = undefined;
+ }
-Localizer.prototype = {
// Configure suitable language based on user preferences
- setup: function (supportedLanguages) {
+ setup(supportedLanguages) {
this.language = 'en'; // Default: US English
/*
return;
}
}
- },
+ }
// Retrieve localised text
- get: function (id) {
+ get(id) {
if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
return this.dictionary[id];
} else {
return id;
}
- },
+ }
// Traverses the DOM and translates relevant fields
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
- translateDOM: function () {
+ translateDOM() {
const self = this;
+
function process(elem, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
process(document.body, true);
- },
-};
+ }
+}
export const l10n = new Localizer();
export default l10n.get.bind(l10n);
reconnect_callback: null,
reconnect_password: null,
- prime: function(callback) {
+ prime(callback) {
if (document.readyState === "interactive" || document.readyState === "complete") {
UI.load(callback);
} else {
// Setup rfb object, load settings from browser storage, then call
// UI.init to setup the UI/menus
- load: function(callback) {
+ load(callback) {
WebUtil.initSettings(UI.start, callback);
},
// Render default UI and initialize settings menu
- start: function(callback) {
+ start(callback) {
// Setup global variables first
UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
}
},
- initFullscreen: function() {
+ initFullscreen() {
// Only show the button if fullscreen is properly supported
// * Safari doesn't support alphanumerical input while in fullscreen
if (!UI.isSafari &&
}
},
- initSettings: function() {
+ initSettings() {
// Logging selection dropdown
const llevels = ['error', 'warn', 'info', 'debug'];
for (let i = 0; i < llevels.length; i += 1) {
UI.setupSettingLabels();
},
// Adds a link to the label elements on the corresponding input elements
- setupSettingLabels: function() {
+ setupSettingLabels() {
const labels = document.getElementsByTagName('LABEL');
for (let i = 0; i < labels.length; i++) {
const htmlFor = labels[i].htmlFor;
* EVENT HANDLERS
* ------v------*/
- addControlbarHandlers: function() {
+ addControlbarHandlers() {
document.getElementById("noVNC_control_bar")
.addEventListener('mousemove', UI.activateControlbar);
document.getElementById("noVNC_control_bar")
}
},
- addTouchSpecificHandlers: function() {
+ addTouchSpecificHandlers() {
document.getElementById("noVNC_mouse_button0")
.addEventListener('click', function () { UI.setMouseButton(1); });
document.getElementById("noVNC_mouse_button1")
.addEventListener('touchmove', UI.dragControlbarHandle);
},
- addExtraKeysHandlers: function() {
+ addExtraKeysHandlers() {
document.getElementById("noVNC_toggle_extra_keys_button")
.addEventListener('click', UI.toggleExtraKeys);
document.getElementById("noVNC_toggle_ctrl_button")
.addEventListener('click', UI.sendCtrlAltDel);
},
- addMachineHandlers: function() {
+ addMachineHandlers() {
document.getElementById("noVNC_shutdown_button")
.addEventListener('click', function() { UI.rfb.machineShutdown(); });
document.getElementById("noVNC_reboot_button")
.addEventListener('click', UI.togglePowerPanel);
},
- addConnectionControlHandlers: function() {
+ addConnectionControlHandlers() {
document.getElementById("noVNC_disconnect_button")
.addEventListener('click', UI.disconnect);
document.getElementById("noVNC_connect_button")
.addEventListener('click', UI.setPassword);
},
- addClipboardHandlers: function() {
+ addClipboardHandlers() {
document.getElementById("noVNC_clipboard_button")
.addEventListener('click', UI.toggleClipboardPanel);
document.getElementById("noVNC_clipboard_text")
// Add a call to save settings when the element changes,
// unless the optional parameter changeFunc is used instead.
- addSettingChangeHandler: function(name, changeFunc) {
+ addSettingChangeHandler(name, changeFunc) {
const settingElem = document.getElementById("noVNC_setting_" + name);
if (changeFunc === undefined) {
changeFunc = function () { UI.saveSetting(name); };
settingElem.addEventListener('change', changeFunc);
},
- addSettingsHandlers: function() {
+ addSettingsHandlers() {
document.getElementById("noVNC_settings_button")
.addEventListener('click', UI.toggleSettingsPanel);
UI.addSettingChangeHandler('reconnect_delay');
},
- addFullscreenHandlers: function() {
+ addFullscreenHandlers() {
document.getElementById("noVNC_fullscreen_button")
.addEventListener('click', UI.toggleFullscreen);
* ------v------*/
// Disable/enable controls depending on connection state
- updateVisualState: function(state) {
+ updateVisualState(state) {
document.documentElement.classList.remove("noVNC_connecting");
document.documentElement.classList.remove("noVNC_connected");
.classList.remove('noVNC_open');
},
- showStatus: function(text, status_type, time) {
+ showStatus(text, status_type, time) {
const statusElem = document.getElementById('noVNC_status');
clearTimeout(UI.statusTimeout);
}
},
- hideStatus: function() {
+ hideStatus() {
clearTimeout(UI.statusTimeout);
document.getElementById('noVNC_status').classList.remove("noVNC_open");
},
- activateControlbar: function(event) {
+ activateControlbar(event) {
clearTimeout(UI.idleControlbarTimeout);
// We manipulate the anchor instead of the actual control
// bar in order to avoid creating new a stacking group
UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
},
- idleControlbar: function() {
+ idleControlbar() {
document.getElementById('noVNC_control_bar_anchor')
.classList.add("noVNC_idle");
},
- keepControlbar: function() {
+ keepControlbar() {
clearTimeout(UI.closeControlbarTimeout);
},
- openControlbar: function() {
+ openControlbar() {
document.getElementById('noVNC_control_bar')
.classList.add("noVNC_open");
},
- closeControlbar: function() {
+ closeControlbar() {
UI.closeAllPanels();
document.getElementById('noVNC_control_bar')
.classList.remove("noVNC_open");
},
- toggleControlbar: function() {
+ toggleControlbar() {
if (document.getElementById('noVNC_control_bar')
.classList.contains("noVNC_open")) {
UI.closeControlbar();
}
},
- toggleControlbarSide: function () {
+ toggleControlbarSide() {
// Temporarily disable animation, if bar is displayed, to avoid weird
// movement. The transitionend-event will not fire when display=none.
const bar = document.getElementById('noVNC_control_bar');
UI.controlbarDrag = true;
},
- showControlbarHint: function (show) {
+ showControlbarHint(show) {
const hint = document.getElementById('noVNC_control_bar_hint');
if (show) {
hint.classList.add("noVNC_active");
}
},
- dragControlbarHandle: function (e) {
+ dragControlbarHandle(e) {
if (!UI.controlbarGrabbed) return;
const ptr = getPointerEvent(e);
},
// Move the handle but don't allow any position outside the bounds
- moveControlbarHandle: function (viewportRelativeY) {
+ moveControlbarHandle(viewportRelativeY) {
const handle = document.getElementById("noVNC_control_bar_handle");
const handleHeight = handle.getBoundingClientRect().height;
const controlbarBounds = document.getElementById("noVNC_control_bar")
handle.style.transform = "translateY(" + parentRelativeY + "px)";
},
- updateControlbarHandle: function () {
+ updateControlbarHandle() {
// Since the control bar is fixed on the viewport and not the page,
// the move function expects coordinates relative the the viewport.
const handle = document.getElementById("noVNC_control_bar_handle");
UI.moveControlbarHandle(handleBounds.top);
},
- controlbarHandleMouseUp: function(e) {
+ controlbarHandleMouseUp(e) {
if ((e.type == "mouseup") && (e.button != 0)) return;
// mouseup and mousedown on the same place toggles the controlbar
UI.showControlbarHint(false);
},
- controlbarHandleMouseDown: function(e) {
+ controlbarHandleMouseDown(e) {
if ((e.type == "mousedown") && (e.button != 0)) return;
const ptr = getPointerEvent(e);
UI.activateControlbar();
},
- toggleExpander: function(e) {
+ toggleExpander(e) {
if (this.classList.contains("noVNC_open")) {
this.classList.remove("noVNC_open");
} else {
* ------v------*/
// Initial page load read/initialization of settings
- initSetting: function(name, defVal) {
+ initSetting(name, defVal) {
// Check Query string followed by cookie
let val = WebUtil.getConfigVar(name);
if (val === null) {
},
// Set the new value, update and disable form control setting
- forceSetting: function(name, val) {
+ forceSetting(name, val) {
WebUtil.setSetting(name, val);
UI.updateSetting(name);
UI.disableSetting(name);
// Update cookie and form control setting. If value is not set, then
// updates from control to current cookie setting.
- updateSetting: function(name) {
+ updateSetting(name) {
// Update the settings control
let value = UI.getSetting(name);
},
// Save control setting to cookie
- saveSetting: function(name) {
+ saveSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name);
let val;
if (ctrl.type === 'checkbox') {
},
// Read form control compatible setting from cookie
- getSetting: function(name) {
+ getSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name);
let val = WebUtil.readSetting(name);
if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
// These helpers compensate for the lack of parent-selectors and
// previous-sibling-selectors in CSS which are needed when we want to
// disable the labels that belong to disabled input elements.
- disableSetting: function(name) {
+ disableSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name);
ctrl.disabled = true;
ctrl.label.classList.add('noVNC_disabled');
},
- enableSetting: function(name) {
+ enableSetting(name) {
const ctrl = document.getElementById('noVNC_setting_' + name);
ctrl.disabled = false;
ctrl.label.classList.remove('noVNC_disabled');
* PANELS
* ------v------*/
- closeAllPanels: function() {
+ closeAllPanels() {
UI.closeSettingsPanel();
UI.closePowerPanel();
UI.closeClipboardPanel();
* SETTINGS (panel)
* ------v------*/
- openSettingsPanel: function() {
+ openSettingsPanel() {
UI.closeAllPanels();
UI.openControlbar();
.classList.add("noVNC_selected");
},
- closeSettingsPanel: function() {
+ closeSettingsPanel() {
document.getElementById('noVNC_settings')
.classList.remove("noVNC_open");
document.getElementById('noVNC_settings_button')
.classList.remove("noVNC_selected");
},
- toggleSettingsPanel: function() {
+ toggleSettingsPanel() {
if (document.getElementById('noVNC_settings')
.classList.contains("noVNC_open")) {
UI.closeSettingsPanel();
* POWER
* ------v------*/
- openPowerPanel: function() {
+ openPowerPanel() {
UI.closeAllPanels();
UI.openControlbar();
.classList.add("noVNC_selected");
},
- closePowerPanel: function() {
+ closePowerPanel() {
document.getElementById('noVNC_power')
.classList.remove("noVNC_open");
document.getElementById('noVNC_power_button')
.classList.remove("noVNC_selected");
},
- togglePowerPanel: function() {
+ togglePowerPanel() {
if (document.getElementById('noVNC_power')
.classList.contains("noVNC_open")) {
UI.closePowerPanel();
},
// Disable/enable power button
- updatePowerButton: function() {
+ updatePowerButton() {
if (UI.connected &&
UI.rfb.capabilities.power &&
!UI.rfb.viewOnly) {
* CLIPBOARD
* ------v------*/
- openClipboardPanel: function() {
+ openClipboardPanel() {
UI.closeAllPanels();
UI.openControlbar();
.classList.add("noVNC_selected");
},
- closeClipboardPanel: function() {
+ closeClipboardPanel() {
document.getElementById('noVNC_clipboard')
.classList.remove("noVNC_open");
document.getElementById('noVNC_clipboard_button')
.classList.remove("noVNC_selected");
},
- toggleClipboardPanel: function() {
+ toggleClipboardPanel() {
if (document.getElementById('noVNC_clipboard')
.classList.contains("noVNC_open")) {
UI.closeClipboardPanel();
}
},
- clipboardReceive: function(e) {
+ clipboardReceive(e) {
Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0,40) + "...");
document.getElementById('noVNC_clipboard_text').value = e.detail.text;
Log.Debug("<< UI.clipboardReceive");
},
- clipboardClear: function() {
+ clipboardClear() {
document.getElementById('noVNC_clipboard_text').value = "";
UI.rfb.clipboardPasteFrom("");
},
- clipboardSend: function() {
+ clipboardSend() {
const text = document.getElementById('noVNC_clipboard_text').value;
Log.Debug(">> UI.clipboardSend: " + text.substr(0,40) + "...");
UI.rfb.clipboardPasteFrom(text);
* CONNECTION
* ------v------*/
- openConnectPanel: function() {
+ openConnectPanel() {
document.getElementById('noVNC_connect_dlg')
.classList.add("noVNC_open");
},
- closeConnectPanel: function() {
+ closeConnectPanel() {
document.getElementById('noVNC_connect_dlg')
.classList.remove("noVNC_open");
},
- connect: function(event, password) {
+ connect(event, password) {
// Ignore when rfb already exists
if (typeof UI.rfb !== 'undefined') {
UI.updateViewOnly(); // requires UI.rfb
},
- disconnect: function() {
+ disconnect() {
UI.closeAllPanels();
UI.rfb.disconnect();
// Don't display the connection settings until we're actually disconnected
},
- reconnect: function() {
+ reconnect() {
UI.reconnect_callback = null;
// if reconnect has been disabled in the meantime, do nothing.
UI.connect(null, UI.reconnect_password);
},
- cancelReconnect: function() {
+ cancelReconnect() {
if (UI.reconnect_callback !== null) {
clearTimeout(UI.reconnect_callback);
UI.reconnect_callback = null;
UI.openConnectPanel();
},
- connectFinished: function (e) {
+ connectFinished(e) {
UI.connected = true;
UI.inhibit_reconnect = false;
UI.rfb.focus();
},
- disconnectFinished: function (e) {
+ disconnectFinished(e) {
const wasConnected = UI.connected;
// This variable is ideally set when disconnection starts, but
UI.openConnectPanel();
},
- securityFailed: function (e) {
+ securityFailed(e) {
let msg = "";
// On security failures we might get a string with a reason
// directly from the server. Note that we can't control if
* PASSWORD
* ------v------*/
- credentials: function(e) {
+ credentials(e) {
// FIXME: handle more types
document.getElementById('noVNC_password_dlg')
.classList.add('noVNC_open');
UI.showStatus(_("Password is required"), "warning");
},
- setPassword: function(e) {
+ setPassword(e) {
// Prevent actually submitting the form
e.preventDefault();
* FULLSCREEN
* ------v------*/
- toggleFullscreen: function() {
+ toggleFullscreen() {
if (document.fullscreenElement || // alternative standard method
document.mozFullScreenElement || // currently working methods
document.webkitFullscreenElement ||
UI.updateFullscreenButton();
},
- updateFullscreenButton: function() {
+ updateFullscreenButton() {
if (document.fullscreenElement || // alternative standard method
document.mozFullScreenElement || // currently working methods
document.webkitFullscreenElement ||
* ------v------*/
// Apply remote resizing or local scaling
- applyResizeMode: function() {
+ applyResizeMode() {
if (!UI.rfb) return;
UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
* ------v------*/
// Update parameters that depend on the viewport clip setting
- updateViewClip: function() {
+ updateViewClip() {
if (!UI.rfb) return;
const cur_clip = UI.rfb.clipViewport;
},
// Handle special cases where viewport clipping is locked
- enableDisableViewClip: function() {
+ enableDisableViewClip() {
const resizeSetting = UI.getSetting('resize');
if (isTouchDevice) {
UI.forceSetting('view_clip', true);
* VIEWDRAG
* ------v------*/
- toggleViewDrag: function() {
+ toggleViewDrag() {
if (!UI.rfb) return;
const drag = UI.rfb.dragViewport;
},
// Set the view drag mode which moves the viewport on mouse drags
- setViewDrag: function(drag) {
+ setViewDrag(drag) {
if (!UI.rfb) return;
UI.rfb.dragViewport = drag;
UI.updateViewDrag();
},
- updateViewDrag: function() {
+ updateViewDrag() {
if (!UI.connected) return;
const viewDragButton = document.getElementById('noVNC_view_drag_button');
* KEYBOARD
* ------v------*/
- showVirtualKeyboard: function() {
+ showVirtualKeyboard() {
if (!isTouchDevice) return;
const input = document.getElementById('noVNC_keyboardinput');
}
},
- hideVirtualKeyboard: function() {
+ hideVirtualKeyboard() {
if (!isTouchDevice) return;
const input = document.getElementById('noVNC_keyboardinput');
input.blur();
},
- toggleVirtualKeyboard: function () {
+ toggleVirtualKeyboard() {
if (document.getElementById('noVNC_keyboard_button')
.classList.contains("noVNC_selected")) {
UI.hideVirtualKeyboard();
}
},
- onfocusVirtualKeyboard: function(event) {
+ onfocusVirtualKeyboard(event) {
document.getElementById('noVNC_keyboard_button')
.classList.add("noVNC_selected");
if (UI.rfb) {
}
},
- onblurVirtualKeyboard: function(event) {
+ onblurVirtualKeyboard(event) {
document.getElementById('noVNC_keyboard_button')
.classList.remove("noVNC_selected");
if (UI.rfb) {
}
},
- keepVirtualKeyboard: function(event) {
+ keepVirtualKeyboard(event) {
const input = document.getElementById('noVNC_keyboardinput');
// Only prevent focus change if the virtual keyboard is active
event.preventDefault();
},
- keyboardinputReset: function() {
+ keyboardinputReset() {
const kbi = document.getElementById('noVNC_keyboardinput');
kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
UI.lastKeyboardinput = kbi.value;
},
- keyEvent: function (keysym, code, down) {
+ keyEvent(keysym, code, down) {
if (!UI.rfb) return;
UI.rfb.sendKey(keysym, code, down);
// the keyboardinput element instead and generate the corresponding key events.
// This code is required since some browsers on Android are inconsistent in
// sending keyCodes in the normal keyboard events when using on screen keyboards.
- keyInput: function(event) {
+ keyInput(event) {
if (!UI.rfb) return;
* EXTRA KEYS
* ------v------*/
- openExtraKeys: function() {
+ openExtraKeys() {
UI.closeAllPanels();
UI.openControlbar();
.classList.add("noVNC_selected");
},
- closeExtraKeys: function() {
+ closeExtraKeys() {
document.getElementById('noVNC_modifiers')
.classList.remove("noVNC_open");
document.getElementById('noVNC_toggle_extra_keys_button')
.classList.remove("noVNC_selected");
},
- toggleExtraKeys: function() {
+ toggleExtraKeys() {
if(document.getElementById('noVNC_modifiers')
.classList.contains("noVNC_open")) {
UI.closeExtraKeys();
}
},
- sendEsc: function() {
+ sendEsc() {
UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
},
- sendTab: function() {
+ sendTab() {
UI.rfb.sendKey(KeyTable.XK_Tab);
},
- toggleCtrl: function() {
+ toggleCtrl() {
const btn = document.getElementById('noVNC_toggle_ctrl_button');
if (btn.classList.contains("noVNC_selected")) {
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
}
},
- toggleAlt: function() {
+ toggleAlt() {
const btn = document.getElementById('noVNC_toggle_alt_button');
if (btn.classList.contains("noVNC_selected")) {
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
}
},
- sendCtrlAltDel: function() {
+ sendCtrlAltDel() {
UI.rfb.sendCtrlAltDel();
},
* MISC
* ------v------*/
- setMouseButton: function(num) {
+ setMouseButton(num) {
const view_only = UI.rfb.viewOnly;
if (UI.rfb && !view_only) {
UI.rfb.touchButton = num;
}
},
- updateViewOnly: function() {
+ updateViewOnly() {
if (!UI.rfb) return;
UI.rfb.viewOnly = UI.getSetting('view_only');
UI.setMouseButton(1); //has it's own logic for hiding/showing
},
- updateLogging: function() {
+ updateLogging() {
WebUtil.init_logging(UI.getSetting('logging'));
},
- updateDesktopName: function(e) {
+ updateDesktopName(e) {
UI.desktopName = e.detail.name;
// Display the desktop name in the document title
document.title = e.detail.name + " - noVNC";
},
- bell: function(e) {
+ bell(e) {
if (WebUtil.getConfigVar('bell', 'on') === 'on') {
const promise = document.getElementById('noVNC_bell').play();
// The standards disagree on the return value here
},
//Helper to add options to dropdown.
- addOption: function(selectbox, text, value) {
+ addOption(selectbox, text, value) {
const optn = document.createElement("OPTION");
optn.text = text;
optn.value = value;
toBase64Table : 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split(''),
base64Pad : '=',
- encode: function (data) {
+ encode(data) {
"use strict";
let result = '';
const length = data.length;
41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
],
- decode: function (data, offset) {
- "use strict";
+ decode(data, offset) {
offset = typeof(offset) !== 'undefined' ? offset : 0;
let data_length = data.indexOf('=') - offset;
import * as Log from './util/logging.js';
import Base64 from "./base64.js";
-export default function Display(target) {
- this._drawCtx = null;
- this._c_forceCanvas = false;
+let SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
+try {
+ new ImageData(new Uint8ClampedArray(4), 1, 1);
+ SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
+} catch (ex) {
+ // ignore failure
+}
- this._renderQ = []; // queue drawing actions for in-oder rendering
- this._flushing = false;
+export default class Display {
+ constructor(target) {
+ this._drawCtx = null;
+ this._c_forceCanvas = false;
- // the full frame buffer (logical canvas) size
- this._fb_width = 0;
- this._fb_height = 0;
+ this._renderQ = []; // queue drawing actions for in-oder rendering
+ this._flushing = false;
- this._prevDrawStyle = "";
- this._tile = null;
- this._tile16x16 = null;
- this._tile_x = 0;
- this._tile_y = 0;
+ // the full frame buffer (logical canvas) size
+ this._fb_width = 0;
+ this._fb_height = 0;
- Log.Debug(">> Display.constructor");
+ this._prevDrawStyle = "";
+ this._tile = null;
+ this._tile16x16 = null;
+ this._tile_x = 0;
+ this._tile_y = 0;
- // The visible canvas
- this._target = target;
+ Log.Debug(">> Display.constructor");
- if (!this._target) {
- throw new Error("Target must be set");
- }
+ // The visible canvas
+ this._target = target;
- if (typeof this._target === 'string') {
- throw new Error('target must be a DOM element');
- }
+ if (!this._target) {
+ throw new Error("Target must be set");
+ }
- if (!this._target.getContext) {
- throw new Error("no getContext method");
- }
+ if (typeof this._target === 'string') {
+ throw new Error('target must be a DOM element');
+ }
- this._targetCtx = this._target.getContext('2d');
+ if (!this._target.getContext) {
+ throw new Error("no getContext method");
+ }
- // the visible canvas viewport (i.e. what actually gets seen)
- this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
+ this._targetCtx = this._target.getContext('2d');
- // The hidden canvas, where we do the actual rendering
- this._backbuffer = document.createElement('canvas');
- this._drawCtx = this._backbuffer.getContext('2d');
+ // the visible canvas viewport (i.e. what actually gets seen)
+ this._viewportLoc = { 'x': 0, 'y': 0, 'w': this._target.width, 'h': this._target.height };
- this._damageBounds = { left:0, top:0,
- right: this._backbuffer.width,
- bottom: this._backbuffer.height };
+ // The hidden canvas, where we do the actual rendering
+ this._backbuffer = document.createElement('canvas');
+ this._drawCtx = this._backbuffer.getContext('2d');
- Log.Debug("User Agent: " + navigator.userAgent);
+ this._damageBounds = { left:0, top:0,
+ right: this._backbuffer.width,
+ bottom: this._backbuffer.height };
- this.clear();
+ Log.Debug("User Agent: " + navigator.userAgent);
- // Check canvas features
- if (!('createImageData' in this._drawCtx)) {
- throw new Error("Canvas does not support createImageData");
- }
+ this.clear();
- this._tile16x16 = this._drawCtx.createImageData(16, 16);
- Log.Debug("<< Display.constructor");
-}
+ // Check canvas features
+ if (!('createImageData' in this._drawCtx)) {
+ throw new Error("Canvas does not support createImageData");
+ }
-let SUPPORTS_IMAGEDATA_CONSTRUCTOR = false;
-try {
- new ImageData(new Uint8ClampedArray(4), 1, 1);
- SUPPORTS_IMAGEDATA_CONSTRUCTOR = true;
-} catch (ex) {
- // ignore failure
-}
+ this._tile16x16 = this._drawCtx.createImageData(16, 16);
+ Log.Debug("<< Display.constructor");
+
+ // ===== PROPERTIES =====
+
+ this._scale = 1.0;
+ this._clipViewport = false;
+ this.logo = null;
+
+ // ===== EVENT HANDLERS =====
+
+ this.onflush = () => {}; // A flush request has finished
+ }
-Display.prototype = {
// ===== PROPERTIES =====
- _scale: 1.0,
- get scale() { return this._scale; },
+ get scale() { return this._scale; }
set scale(scale) {
this._rescale(scale);
- },
+ }
- _clipViewport: false,
- get clipViewport() { return this._clipViewport; },
+ get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
this._clipViewport = viewport;
// May need to readjust the viewport dimensions
const 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;
- },
-
- logo: null,
-
- // ===== EVENT HANDLERS =====
-
- onflush: function () {}, // A flush request has finished
+ }
// ===== PUBLIC METHODS =====
- viewportChangePos: function (deltaX, deltaY) {
+ viewportChangePos(deltaX, deltaY) {
const vp = this._viewportLoc;
deltaX = Math.floor(deltaX);
deltaY = Math.floor(deltaY);
this._damage(vp.x, vp.y, vp.w, vp.h);
this.flip();
- },
+ }
- viewportChangeSize: function(width, height) {
+ viewportChangeSize(width, height) {
if (!this._clipViewport ||
typeof(width) === "undefined" ||
// Update the visible size of the target canvas
this._rescale(this._scale);
}
- },
+ }
- absX: function (x) {
+ absX(x) {
return x / this._scale + this._viewportLoc.x;
- },
+ }
- absY: function (y) {
+ absY(y) {
return y / this._scale + this._viewportLoc.y;
- },
+ }
- resize: function (width, height) {
+ resize(width, height) {
this._prevDrawStyle = "";
this._fb_width = width;
const vp = this._viewportLoc;
this.viewportChangeSize(vp.w, vp.h);
this.viewportChangePos(0, 0);
- },
+ }
// Track what parts of the visible canvas that need updating
- _damage: function(x, y, w, h) {
+ _damage(x, y, w, h) {
if (x < this._damageBounds.left) {
this._damageBounds.left = x;
}
if ((y + h) > this._damageBounds.bottom) {
this._damageBounds.bottom = y + h;
}
- },
+ }
// Update the visible canvas with the contents of the
// rendering canvas
- flip: function(from_queue) {
+ flip(from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'flip'
this._damageBounds.left = this._damageBounds.top = 65535;
this._damageBounds.right = this._damageBounds.bottom = 0;
}
- },
+ }
- clear: function () {
+ clear() {
if (this._logo) {
this.resize(this._logo.width, this._logo.height);
this.imageRect(0, 0, this._logo.type, this._logo.data);
this._drawCtx.clearRect(0, 0, this._fb_width, this._fb_height);
}
this.flip();
- },
+ }
- pending: function() {
+ pending() {
return this._renderQ.length > 0;
- },
+ }
- flush: function() {
+ flush() {
if (this._renderQ.length === 0) {
this.onflush();
} else {
this._flushing = true;
}
- },
+ }
- fillRect: function (x, y, width, height, color, from_queue) {
+ fillRect(x, y, width, height, color, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'fill',
this._drawCtx.fillRect(x, y, width, height);
this._damage(x, y, width, height);
}
- },
+ }
- copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
+ copyImage(old_x, old_y, new_x, new_y, w, h, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this._renderQ_push({
'type': 'copy',
new_x, new_y, w, h);
this._damage(new_x, new_y, w, h);
}
- },
+ }
- imageRect: function(x, y, mime, arr) {
+ imageRect(x, y, mime, arr) {
const img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._renderQ_push({
'x': x,
'y': y
});
- },
+ }
// start updating a tile
- startTile: function (x, y, width, height, color) {
+ startTile(x, y, width, height, color) {
this._tile_x = x;
this._tile_y = y;
if (width === 16 && height === 16) {
data[i + 2] = blue;
data[i + 3] = 255;
}
- },
+ }
// update sub-rectangle of the current tile
- subTile: function (x, y, w, h, color) {
+ subTile(x, y, w, h, color) {
const red = color[2];
const green = color[1];
const blue = color[0];
data[p + 3] = 255;
}
}
- },
+ }
// draw the current tile to the screen
- finishTile: function () {
+ finishTile() {
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) {
+ blitImage(x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
} else {
this._bgrxImageData(x, y, width, height, arr, offset);
}
- },
+ }
- blitRgbImage: function (x, y , width, height, arr, offset, from_queue) {
+ blitRgbImage(x, y , width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
} else {
this._rgbImageData(x, y, width, height, arr, offset);
}
- },
+ }
- blitRgbxImage: function (x, y, width, height, arr, offset, from_queue) {
+ blitRgbxImage(x, y, width, height, arr, offset, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
// NB(directxman12): it's technically more performant here to use preallocated arrays,
// but it's a lot of extra work for not a lot of payoff -- if we're using the render queue,
} else {
this._rgbxImageData(x, y, width, height, arr, offset);
}
- },
+ }
- drawImage: function (img, x, y) {
+ drawImage(img, x, y) {
this._drawCtx.drawImage(img, x, y);
this._damage(x, y, img.width, img.height);
- },
+ }
- autoscale: function (containerWidth, containerHeight) {
+ autoscale(containerWidth, containerHeight) {
const vp = this._viewportLoc;
const targetAspectRatio = containerWidth / containerHeight;
const fbAspectRatio = vp.w / vp.h;
}
this._rescale(scaleRatio);
- },
+ }
// ===== PRIVATE METHODS =====
- _rescale: function (factor) {
+ _rescale(factor) {
this._scale = factor;
const vp = this._viewportLoc;
this._target.style.width = width;
this._target.style.height = height;
}
- },
+ }
- _setFillColor: function (color) {
+ _setFillColor(color) {
const newStyle = 'rgb(' + color[2] + ',' + color[1] + ',' + color[0] + ')';
if (newStyle !== this._prevDrawStyle) {
this._drawCtx.fillStyle = newStyle;
this._prevDrawStyle = newStyle;
}
- },
+ }
- _rgbImageData: function (x, y, width, height, arr, offset) {
+ _rgbImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 3) {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
- },
+ }
- _bgrxImageData: function (x, y, width, height, arr, offset) {
+ _bgrxImageData(x, y, width, height, arr, offset) {
const img = this._drawCtx.createImageData(width, height);
const data = img.data;
for (let i = 0, j = offset; i < width * height * 4; i += 4, j += 4) {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
- },
+ }
- _rgbxImageData: function (x, y, width, height, arr, offset) {
+ _rgbxImageData(x, y, width, height, arr, offset) {
// NB(directxman12): arr must be an Type Array view
let img;
if (SUPPORTS_IMAGEDATA_CONSTRUCTOR) {
}
this._drawCtx.putImageData(img, x, y);
this._damage(x, y, img.width, img.height);
- },
+ }
- _renderQ_push: function (action) {
+ _renderQ_push(action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will wait for the relevant event
this._scan_renderQ();
}
- },
+ }
- _resume_renderQ: function() {
+ _resume_renderQ() {
// "this" is the object that is ready, not the
// display object
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
this._noVNC_display._scan_renderQ();
- },
+ }
- _scan_renderQ: function () {
+ _scan_renderQ() {
let ready = true;
while (ready && this._renderQ.length > 0) {
const a = this._renderQ[0];
this._flushing = false;
this.onflush();
}
- },
-};
+ }
+}
import { inflateInit, inflate, inflateReset } from "../vendor/pako/lib/zlib/inflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
-Inflate.prototype = {
- inflate: function (data, flush, expected) {
+export default class Inflate {
+ constructor() {
+ this.strm = new ZStream();
+ this.chunkSize = 1024 * 10 * 10;
+ this.strm.output = new Uint8Array(this.chunkSize);
+ this.windowBits = 5;
+
+ inflateInit(this.strm, this.windowBits);
+ }
+
+ inflate(data, flush, expected) {
this.strm.input = data;
this.strm.avail_in = this.strm.input.length;
this.strm.next_in = 0;
inflate(this.strm, flush);
return new Uint8Array(this.strm.output.buffer, 0, this.strm.next_out);
- },
+ }
- reset: function () {
+ reset() {
inflateReset(this.strm);
}
-};
-
-export default function Inflate() {
- this.strm = new ZStream();
- this.chunkSize = 1024 * 10 * 10;
- this.strm.output = new Uint8Array(this.chunkSize);
- this.windowBits = 5;
-
- inflateInit(this.strm, this.windowBits);
}
// Keyboard event handler
//
-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
- this._altGrArmed = false; // Windows AltGr detection
-
- // keep these here so we can refer to them later
- this._eventHandlers = {
- 'keyup': this._handleKeyUp.bind(this),
- 'keydown': this._handleKeyDown.bind(this),
- 'keypress': this._handleKeyPress.bind(this),
- 'blur': this._allKeysUp.bind(this),
- 'checkalt': this._checkAlt.bind(this),
- };
-}
+export default class Keyboard {
+ constructor(target) {
+ this._target = target || null;
+
+ this._keyDownList = {}; // List of depressed keys
+ // (even if they are happy)
+ this._pendingKey = null; // Key waiting for keypress
+ this._altGrArmed = false; // Windows AltGr detection
+
+ // keep these here so we can refer to them later
+ this._eventHandlers = {
+ 'keyup': this._handleKeyUp.bind(this),
+ 'keydown': this._handleKeyDown.bind(this),
+ 'keypress': this._handleKeyPress.bind(this),
+ 'blur': this._allKeysUp.bind(this),
+ 'checkalt': this._checkAlt.bind(this),
+ };
-Keyboard.prototype = {
- // ===== EVENT HANDLERS =====
+ // ===== EVENT HANDLERS =====
- onkeyevent: function () {}, // Handler for key press/release
+ this.onkeyevent = () => {}; // Handler for key press/release
+ }
// ===== PRIVATE METHODS =====
- _sendKeyEvent: function (keysym, code, down) {
+ _sendKeyEvent(keysym, code, down) {
if (down) {
this._keyDownList[code] = keysym;
} else {
Log.Debug("onkeyevent " + (down ? "down" : "up") +
", keysym: " + keysym, ", code: " + code);
this.onkeyevent(keysym, code, down);
- },
+ }
- _getKeyCode: function (e) {
+ _getKeyCode(e) {
const code = KeyboardUtil.getKeycode(e);
if (code !== 'Unidentified') {
return code;
}
return 'Unidentified';
- },
+ }
- _handleKeyDown: function (e) {
+ _handleKeyDown(e) {
const code = this._getKeyCode(e);
let keysym = KeyboardUtil.getKeysym(e);
}
this._sendKeyEvent(keysym, code, true);
- },
+ }
// Legacy event for browsers without code/key
- _handleKeyPress: function (e) {
+ _handleKeyPress(e) {
stopEvent(e);
// Are we expecting a keypress?
}
this._sendKeyEvent(keysym, code, true);
- },
- _handleKeyPressTimeout: function (e) {
+ }
+
+ _handleKeyPressTimeout(e) {
// Did someone manage to sort out the key already?
if (this._pendingKey === null) {
return;
}
this._sendKeyEvent(keysym, code, true);
- },
+ }
- _handleKeyUp: function (e) {
+ _handleKeyUp(e) {
stopEvent(e);
const code = this._getKeyCode(e);
}
this._sendKeyEvent(this._keyDownList[code], code, false);
- },
+ }
- _handleAltGrTimeout: function () {
+ _handleAltGrTimeout() {
this._altGrArmed = false;
clearTimeout(this._altGrTimeout);
this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);
- },
+ }
- _allKeysUp: function () {
+ _allKeysUp() {
Log.Debug(">> Keyboard.allKeysUp");
for (let code in this._keyDownList) {
this._sendKeyEvent(this._keyDownList[code], code, false);
}
Log.Debug("<< Keyboard.allKeysUp");
- },
+ }
// Firefox Alt workaround, see below
- _checkAlt: function (e) {
+ _checkAlt(e) {
if (e.altKey) {
return;
}
code: code });
target.dispatchEvent(event);
});
- },
+ }
// ===== PUBLIC METHODS =====
- grab: function () {
+ grab() {
//Log.Debug(">> Keyboard.grab");
this._target.addEventListener('keydown', this._eventHandlers.keydown);
document.addEventListener(type, handler,
{ capture: true,
passive: true });
- });
+ });
}
//Log.Debug("<< Keyboard.grab");
- },
+ }
- ungrab: function () {
+ ungrab() {
//Log.Debug(">> Keyboard.ungrab");
if (browser.isWindows() && browser.isFirefox()) {
this._allKeysUp();
//Log.Debug(">> Keyboard.ungrab");
- },
-};
+ }
+}
};
export default {
- lookup : function(u) {
+ lookup(u) {
// Latin-1 is one-to-one mapping
if ((u >= 0x20) && (u <= 0xff)) {
return u;
const WHEEL_STEP_TIMEOUT = 50; // ms
const WHEEL_LINE_HEIGHT = 19;
-export default function Mouse(target) {
- this._target = target || document;
-
- this._doubleClickTimer = null;
- this._lastTouchPos = null;
-
- this._pos = null;
- this._wheelStepXTimer = null;
- this._wheelStepYTimer = null;
- this._accumulatedWheelDeltaX = 0;
- this._accumulatedWheelDeltaY = 0;
-
- this._eventHandlers = {
- 'mousedown': this._handleMouseDown.bind(this),
- 'mouseup': this._handleMouseUp.bind(this),
- 'mousemove': this._handleMouseMove.bind(this),
- 'mousewheel': this._handleMouseWheel.bind(this),
- 'mousedisable': this._handleMouseDisable.bind(this)
- };
-}
+export default class Mouse {
+ constructor(target) {
+ this._target = target || document;
+
+ this._doubleClickTimer = null;
+ this._lastTouchPos = null;
+
+ this._pos = null;
+ this._wheelStepXTimer = null;
+ this._wheelStepYTimer = null;
+ this._accumulatedWheelDeltaX = 0;
+ this._accumulatedWheelDeltaY = 0;
+
+ this._eventHandlers = {
+ 'mousedown': this._handleMouseDown.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mousewheel': this._handleMouseWheel.bind(this),
+ 'mousedisable': this._handleMouseDisable.bind(this)
+ };
-Mouse.prototype = {
- // ===== PROPERTIES =====
+ // ===== PROPERTIES =====
- touchButton: 1, // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
+ this.touchButton = 1; // Button mask (1, 2, 4) for touch devices (0 means ignore clicks)
- // ===== EVENT HANDLERS =====
+ // ===== EVENT HANDLERS =====
- onmousebutton: function () {}, // Handler for mouse button click/release
- onmousemove: function () {}, // Handler for mouse movement
+ this.onmousebutton = () => {}; // Handler for mouse button click/release
+ this.onmousemove = () => {}; // Handler for mouse movement
+ }
// ===== PRIVATE METHODS =====
- _resetDoubleClickTimer: function () {
+ _resetDoubleClickTimer() {
this._doubleClickTimer = null;
- },
+ }
- _handleMouseButton: function (e, down) {
+ _handleMouseButton(e, down) {
this._updateMousePosition(e);
let pos = this._pos;
this.onmousebutton(pos.x, pos.y, down, bmask);
stopEvent(e);
- },
+ }
- _handleMouseDown: function (e) {
+ _handleMouseDown(e) {
// Touch events have implicit capture
if (e.type === "mousedown") {
setCapture(this._target);
}
this._handleMouseButton(e, 1);
- },
+ }
- _handleMouseUp: function (e) {
+ _handleMouseUp(e) {
this._handleMouseButton(e, 0);
- },
+ }
// Mouse wheel events are sent in steps over VNC. This means that the VNC
// protocol can't handle a wheel event with specific distance or speed.
// Therefor, if we get a lot of small mouse wheel events we combine them.
- _generateWheelStepX: function () {
+ _generateWheelStepX() {
if (this._accumulatedWheelDeltaX < 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5);
}
this._accumulatedWheelDeltaX = 0;
- },
+ }
- _generateWheelStepY: function () {
+ _generateWheelStepY() {
if (this._accumulatedWheelDeltaY < 0) {
this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3);
}
this._accumulatedWheelDeltaY = 0;
- },
+ }
- _resetWheelStepTimers: function () {
+ _resetWheelStepTimers() {
window.clearTimeout(this._wheelStepXTimer);
window.clearTimeout(this._wheelStepYTimer);
this._wheelStepXTimer = null;
this._wheelStepYTimer = null;
- },
+ }
- _handleMouseWheel: function (e) {
+ _handleMouseWheel(e) {
this._resetWheelStepTimers();
this._updateMousePosition(e);
}
stopEvent(e);
- },
+ }
- _handleMouseMove: function (e) {
+ _handleMouseMove(e) {
this._updateMousePosition(e);
this.onmousemove(this._pos.x, this._pos.y);
stopEvent(e);
- },
+ }
- _handleMouseDisable: function (e) {
+ _handleMouseDisable(e) {
/*
* Stop propagation if inside canvas area
* Note: This is only needed for the 'click' event as it fails
if (e.target == this._target) {
stopEvent(e);
}
- },
+ }
// Update coordinates relative to target
- _updateMousePosition: function(e) {
+ _updateMousePosition(e) {
e = getPointerEvent(e);
const bounds = this._target.getBoundingClientRect();
let x;
y = e.clientY - bounds.top;
}
this._pos = {x:x, y:y};
- },
+ }
// ===== PUBLIC METHODS =====
- grab: function () {
+ grab() {
const c = this._target;
if (isTouchDevice) {
/* preventDefault() on mousedown doesn't stop this event for some
reason so we have to explicitly block it */
c.addEventListener('contextmenu', this._eventHandlers.mousedisable);
- },
+ }
- ungrab: function () {
+ ungrab() {
const c = this._target;
this._resetWheelStepTimers();
c.removeEventListener('contextmenu', this._eventHandlers.mousedisable);
}
-};
+}
// How many seconds to wait for a disconnect to finish
const DISCONNECT_TIMEOUT = 3;
-export default function RFB(target, url, options) {
- if (!target) {
- throw Error("Must specify target");
- }
- if (!url) {
- throw Error("Must specify URL");
- }
+export default class RFB extends EventTargetMixin {
+ constructor(target, url, options) {
+ if (!target) {
+ throw Error("Must specify target");
+ }
+ if (!url) {
+ throw Error("Must specify URL");
+ }
+
+ super();
+
+ 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_auth_scheme = '';
+ this._rfb_clean_disconnect = true;
+
+ // Server capabilities
+ this._rfb_version = 0;
+ this._rfb_max_version = 3.8;
+ this._rfb_tightvnc = false;
+ this._rfb_xvp_ver = 0;
+
+ this._fb_width = 0;
+ this._fb_height = 0;
+
+ this._fb_name = "";
+
+ this._capabilities = { power: false };
+
+ this._supportsFence = false;
+
+ this._supportsContinuousUpdates = false;
+ this._enabledContinuousUpdates = false;
+
+ 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
+ this._resizeTimeout = null; // resize rate limiting
+
+ // Decoder states and stats
+ this._encHandlers = {};
+ this._encStats = {};
+
+ this._FBU = {
+ rects: 0,
+ subrects: 0, // RRE and HEXTILE
+ lines: 0, // RAW
+ tiles: 0, // HEXTILE
+ bytes: 0,
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ encoding: 0,
+ subencoding: -1,
+ background: null,
+ zlibs: [] // TIGHT zlib streams
+ };
- 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_auth_scheme = '';
- this._rfb_clean_disconnect = true;
-
- // Server capabilities
- this._rfb_version = 0;
- this._rfb_max_version = 3.8;
- this._rfb_tightvnc = false;
- this._rfb_xvp_ver = 0;
-
- this._fb_width = 0;
- this._fb_height = 0;
-
- this._fb_name = "";
-
- this._capabilities = { power: false };
-
- this._supportsFence = false;
-
- this._supportsContinuousUpdates = false;
- this._enabledContinuousUpdates = false;
-
- 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
- this._resizeTimeout = null; // resize rate limiting
-
- // Decoder states and stats
- this._encHandlers = {};
- this._encStats = {};
-
- this._FBU = {
- rects: 0,
- subrects: 0, // RRE and HEXTILE
- lines: 0, // RAW
- tiles: 0, // HEXTILE
- bytes: 0,
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- encoding: 0,
- subencoding: -1,
- background: null,
- zlibs: [] // TIGHT zlib streams
- };
- for (let i = 0; i < 4; i++) {
- this._FBU.zlibs[i] = new Inflator();
- }
+ for (let 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)
- this._destBuff = null;
- this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
-
- this._rre_chunk_sz = 100;
-
- this._timing = {
- last_fbu: 0,
- fbu_total: 0,
- fbu_total_cnt: 0,
- full_fbu_total: 0,
- full_fbu_cnt: 0,
-
- fbu_rt_start: 0,
- fbu_rt_total: 0,
- fbu_rt_cnt: 0,
- pixels: 0
- };
-
- // Mouse state
- this._mouse_buttonMask = 0;
- this._mouse_arr = [];
- this._viewportDragging = false;
- this._viewportDragPos = {};
- this._viewportHasMoved = false;
-
- // Bound event handlers
- this._eventHandlers = {
- focusCanvas: this._focusCanvas.bind(this),
- windowResize: this._windowResize.bind(this),
- };
-
- // main setup
- Log.Debug(">> RFB.constructor");
-
- // Create DOM elements
- this._screen = document.createElement('div');
- this._screen.style.display = 'flex';
- this._screen.style.width = '100%';
- this._screen.style.height = '100%';
- this._screen.style.overflow = 'auto';
- this._screen.style.backgroundColor = 'rgb(40, 40, 40)';
- this._canvas = document.createElement('canvas');
- this._canvas.style.margin = 'auto';
- // Some browsers add an outline on focus
- this._canvas.style.outline = 'none';
- // IE miscalculates width without this :(
- this._canvas.style.flexShrink = '0';
- this._canvas.width = 0;
- this._canvas.height = 0;
- this._canvas.tabIndex = -1;
- this._screen.appendChild(this._canvas);
+ this._rre_chunk_sz = 100;
+
+ this._timing = {
+ last_fbu: 0,
+ fbu_total: 0,
+ fbu_total_cnt: 0,
+ full_fbu_total: 0,
+ full_fbu_cnt: 0,
+
+ fbu_rt_start: 0,
+ fbu_rt_total: 0,
+ fbu_rt_cnt: 0,
+ pixels: 0
+ };
+
+ // Mouse state
+ this._mouse_buttonMask = 0;
+ this._mouse_arr = [];
+ this._viewportDragging = false;
+ this._viewportDragPos = {};
+ this._viewportHasMoved = false;
+
+ // Bound event handlers
+ this._eventHandlers = {
+ focusCanvas: this._focusCanvas.bind(this),
+ windowResize: this._windowResize.bind(this),
+ };
+
+ // main setup
+ Log.Debug(">> RFB.constructor");
+
+ // Create DOM elements
+ this._screen = document.createElement('div');
+ this._screen.style.display = 'flex';
+ this._screen.style.width = '100%';
+ this._screen.style.height = '100%';
+ this._screen.style.overflow = 'auto';
+ this._screen.style.backgroundColor = 'rgb(40, 40, 40)';
+ this._canvas = document.createElement('canvas');
+ this._canvas.style.margin = 'auto';
+ // Some browsers add an outline on focus
+ this._canvas.style.outline = 'none';
+ // IE miscalculates width without this :(
+ this._canvas.style.flexShrink = '0';
+ this._canvas.width = 0;
+ this._canvas.height = 0;
+ this._canvas.tabIndex = -1;
+ this._screen.appendChild(this._canvas);
this._cursor = new Cursor();
- // populate encHandlers with bound versions
- this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
- this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
- this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
- this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
- this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this, false);
- this._encHandlers[encodings.encodingTightPNG] = RFB.encodingHandlers.TIGHT.bind(this, true);
-
- this._encHandlers[encodings.pseudoEncodingDesktopSize] = RFB.encodingHandlers.DesktopSize.bind(this);
- this._encHandlers[encodings.pseudoEncodingLastRect] = RFB.encodingHandlers.last_rect.bind(this);
- this._encHandlers[encodings.pseudoEncodingCursor] = RFB.encodingHandlers.Cursor.bind(this);
- this._encHandlers[encodings.pseudoEncodingQEMUExtendedKeyEvent] = RFB.encodingHandlers.QEMUExtendedKeyEvent.bind(this);
- this._encHandlers[encodings.pseudoEncodingExtendedDesktopSize] = RFB.encodingHandlers.ExtendedDesktopSize.bind(this);
-
- // NB: nothing that needs explicit teardown should be done
- // before this point, since this can throw an exception
- try {
- this._display = new Display(this._canvas);
- } catch (exc) {
- Log.Error("Display exception: " + exc);
- throw exc;
- }
- this._display.onflush = this._onFlush.bind(this);
- this._display.clear();
-
- this._keyboard = new Keyboard(this._canvas);
- this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
-
- this._mouse = new Mouse(this._canvas);
- 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));
- this._sock.on('open', function () {
- if ((this._rfb_connection_state === 'connecting') &&
- (this._rfb_init_state === '')) {
- this._rfb_init_state = 'ProtocolVersion';
- Log.Debug("Starting VNC handshake");
- } else {
- this._fail("Unexpected server connection while " +
- this._rfb_connection_state);
- }
- }.bind(this));
- this._sock.on('close', function (e) {
- Log.Debug("WebSocket on-close event");
- let msg = "";
- if (e.code) {
- msg = "(code: " + e.code;
- if (e.reason) {
- msg += ", reason: " + e.reason;
+ // populate encHandlers with bound versions
+ this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this);
+ this._encHandlers[encodings.encodingCopyRect] = RFB.encodingHandlers.COPYRECT.bind(this);
+ this._encHandlers[encodings.encodingRRE] = RFB.encodingHandlers.RRE.bind(this);
+ this._encHandlers[encodings.encodingHextile] = RFB.encodingHandlers.HEXTILE.bind(this);
+ this._encHandlers[encodings.encodingTight] = RFB.encodingHandlers.TIGHT.bind(this, false);
+ this._encHandlers[encodings.encodingTightPNG] = RFB.encodingHandlers.TIGHT.bind(this, true);
+
+ this._encHandlers[encodings.pseudoEncodingDesktopSize] = RFB.encodingHandlers.DesktopSize.bind(this);
+ this._encHandlers[encodings.pseudoEncodingLastRect] = RFB.encodingHandlers.last_rect.bind(this);
+ this._encHandlers[encodings.pseudoEncodingCursor] = RFB.encodingHandlers.Cursor.bind(this);
+ this._encHandlers[encodings.pseudoEncodingQEMUExtendedKeyEvent] = RFB.encodingHandlers.QEMUExtendedKeyEvent.bind(this);
+ this._encHandlers[encodings.pseudoEncodingExtendedDesktopSize] = RFB.encodingHandlers.ExtendedDesktopSize.bind(this);
+
+ // NB: nothing that needs explicit teardown should be done
+ // before this point, since this can throw an exception
+ try {
+ this._display = new Display(this._canvas);
+ } catch (exc) {
+ Log.Error("Display exception: " + exc);
+ throw exc;
+ }
+ this._display.onflush = this._onFlush.bind(this);
+ this._display.clear();
+
+ this._keyboard = new Keyboard(this._canvas);
+ this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
+
+ this._mouse = new Mouse(this._canvas);
+ 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));
+ this._sock.on('open', function () {
+ if ((this._rfb_connection_state === 'connecting') &&
+ (this._rfb_init_state === '')) {
+ this._rfb_init_state = 'ProtocolVersion';
+ Log.Debug("Starting VNC handshake");
+ } else {
+ this._fail("Unexpected server connection while " +
+ this._rfb_connection_state);
}
- msg += ")";
- }
- switch (this._rfb_connection_state) {
- case 'connecting':
- this._fail("Connection closed " + msg);
- break;
- case 'connected':
- // Handle disconnects that were initiated server-side
- this._updateConnectionState('disconnecting');
- this._updateConnectionState('disconnected');
- break;
- case 'disconnecting':
- // Normal disconnection path
- this._updateConnectionState('disconnected');
- break;
- case 'disconnected':
- this._fail("Unexpected server disconnect " +
- "when already disconnected " + msg);
- break;
- default:
- this._fail("Unexpected server disconnect before connecting " +
- msg);
- break;
- }
- this._sock.off('close');
- }.bind(this));
- this._sock.on('error', function (e) {
- Log.Warn("WebSocket on-error event");
- });
+ }.bind(this));
+ this._sock.on('close', function (e) {
+ Log.Debug("WebSocket on-close event");
+ let msg = "";
+ if (e.code) {
+ msg = "(code: " + e.code;
+ if (e.reason) {
+ msg += ", reason: " + e.reason;
+ }
+ msg += ")";
+ }
+ switch (this._rfb_connection_state) {
+ case 'connecting':
+ this._fail("Connection closed " + msg);
+ break;
+ case 'connected':
+ // Handle disconnects that were initiated server-side
+ this._updateConnectionState('disconnecting');
+ this._updateConnectionState('disconnected');
+ break;
+ case 'disconnecting':
+ // Normal disconnection path
+ this._updateConnectionState('disconnected');
+ break;
+ case 'disconnected':
+ this._fail("Unexpected server disconnect " +
+ "when already disconnected " + msg);
+ break;
+ default:
+ this._fail("Unexpected server disconnect before connecting " +
+ msg);
+ break;
+ }
+ this._sock.off('close');
+ }.bind(this));
+ this._sock.on('error', function (e) {
+ Log.Warn("WebSocket on-error event");
+ });
- // Slight delay of the actual connection so that the caller has
- // time to set up callbacks
- setTimeout(this._updateConnectionState.bind(this, 'connecting'));
+ // 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");
-}
+ Log.Debug("<< RFB.constructor");
-RFB.prototype = {
- // ===== PROPERTIES =====
+ // ===== PROPERTIES =====
- dragViewport: false,
- focusOnClick: true,
+ this.dragViewport = false;
+ this.focusOnClick = true;
- _viewOnly: false,
- get viewOnly() { return this._viewOnly; },
+ this._viewOnly = false;
+ this._clipViewport = false;
+ this._scaleViewport = false;
+ this._resizeSession = false;
+ }
+
+ // ===== PROPERTIES =====
+
+ get viewOnly() { return this._viewOnly; }
set viewOnly(viewOnly) {
this._viewOnly = viewOnly;
this._mouse.grab();
}
}
- },
+ }
- get capabilities() { return this._capabilities; },
+ get capabilities() { return this._capabilities; }
- get touchButton() { return this._mouse.touchButton; },
- set touchButton(button) { this._mouse.touchButton = button; },
+ get touchButton() { return this._mouse.touchButton; }
+ set touchButton(button) { this._mouse.touchButton = button; }
- _clipViewport: false,
- get clipViewport() { return this._clipViewport; },
+ get clipViewport() { return this._clipViewport; }
set clipViewport(viewport) {
this._clipViewport = viewport;
this._updateClip();
- },
+ }
- _scaleViewport: false,
- get scaleViewport() { return this._scaleViewport; },
+ get scaleViewport() { return this._scaleViewport; }
set scaleViewport(scale) {
this._scaleViewport = scale;
// Scaling trumps clipping, so we may need to adjust
if (!scale && this._clipViewport) {
this._updateClip();
}
- },
+ }
- _resizeSession: false,
- get resizeSession() { return this._resizeSession; },
+ get resizeSession() { return this._resizeSession; }
set resizeSession(resize) {
this._resizeSession = resize;
if (resize) {
this._requestRemoteResize();
}
- },
+ }
// ===== PUBLIC METHODS =====
- disconnect: function () {
+ disconnect() {
this._updateConnectionState('disconnecting');
this._sock.off('error');
this._sock.off('message');
this._sock.off('open');
- },
+ }
- sendCredentials: function (creds) {
+ sendCredentials(creds) {
this._rfb_credentials = creds;
setTimeout(this._init_msg.bind(this), 0);
- },
+ }
- sendCtrlAltDel: function () {
+ sendCtrlAltDel() {
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
Log.Info("Sending Ctrl-Alt-Del");
this.sendKey(KeyTable.XK_Delete, "Delete", false);
this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
- },
+ }
- machineShutdown: function () {
+ machineShutdown() {
this._xvpOp(1, 2);
- },
+ }
- machineReboot: function () {
+ machineReboot() {
this._xvpOp(1, 3);
- },
+ }
- machineReset: function () {
+ machineReset() {
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) {
+ sendKey(keysym, code, down) {
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
if (down === undefined) {
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
}
- },
+ }
- focus: function () {
+ focus() {
this._canvas.focus();
- },
+ }
- blur: function () {
+ blur() {
this._canvas.blur();
- },
+ }
- clipboardPasteFrom: function (text) {
+ clipboardPasteFrom(text) {
if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; }
RFB.messages.clientCutText(this._sock, text);
- },
+ }
// ===== PRIVATE METHODS =====
- _connect: function () {
+ _connect() {
Log.Debug(">> RFB.connect");
Log.Info("connecting to " + this._url);
this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas);
Log.Debug("<< RFB.connect");
- },
+ }
- _disconnect: function () {
+ _disconnect() {
Log.Debug(">> RFB.disconnect");
this._cursor.detach();
this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas);
}
clearTimeout(this._resizeTimeout);
Log.Debug("<< RFB.disconnect");
- },
+ }
- _print_stats: function () {
+ _print_stats() {
const stats = this._encStats;
Log.Info("Encoding stats for this connection:");
const s = stats[key];
Log.Info(" " + encodingName(key) + ": " + s[1] + " rects");
});
- },
+ }
- _focusCanvas: function(event) {
+ _focusCanvas(event) {
// Respect earlier handlers' request to not do side-effects
if (event.defaultPrevented) {
return;
}
this.focus();
- },
+ }
- _windowResize: function (event) {
+ _windowResize(event) {
// If the window resized then our screen element might have
// as well. Update the viewport dimensions.
window.requestAnimationFrame(function () {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500);
}
- },
+ }
// Update state of clipping in Display object, and make sure the
// configured viewport matches the current screen size
- _updateClip: function () {
+ _updateClip() {
const cur_clip = this._display.clipViewport;
let new_clip = this._clipViewport;
this._display.viewportChangeSize(size.w, size.h);
this._fixScrollbars();
}
- },
+ }
- _updateScale: function () {
+ _updateScale() {
if (!this._scaleViewport) {
this._display.scale = 1.0;
} else {
this._display.autoscale(size.w, size.h);
}
this._fixScrollbars();
- },
+ }
// Requests a change of remote desktop size. This message is an extension
// and may only be sent if we have received an ExtendedDesktopSize message
- _requestRemoteResize: function () {
+ _requestRemoteResize() {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = null;
Log.Debug('Requested new desktop size: ' +
size.w + 'x' + size.h);
- },
+ }
// Gets the the size of the available screen
- _screenSize: function () {
+ _screenSize() {
return { w: this._screen.offsetWidth,
h: this._screen.offsetHeight };
- },
+ }
- _fixScrollbars: function () {
+ _fixScrollbars() {
// This is a hack because Chrome screws up the calculation
// for when scrollbars are needed. So to fix it we temporarily
// toggle them off and on.
// an element's dimensions
this._screen.getBoundingClientRect();
this._screen.style.overflow = orig;
- },
+ }
/*
* Connection states:
* disconnecting
* disconnected - permanent state
*/
- _updateConnectionState: function (state) {
+ _updateConnectionState(state) {
const oldstate = this._rfb_connection_state;
if (state === oldstate) {
{ clean: this._rfb_clean_disconnect } }));
break;
}
- },
+ }
/* Print errors and disconnect
*
* The parameter 'details' is used for information that
* should be logged but not sent to the user interface.
*/
- _fail: function (details) {
+ _fail(details) {
switch (this._rfb_connection_state) {
case 'disconnecting':
Log.Error("Failed when disconnecting: " + details);
this._updateConnectionState('disconnected');
return false;
- },
+ }
- _setCapability: function (cap, val) {
+ _setCapability(cap, val) {
this._capabilities[cap] = val;
this.dispatchEvent(new CustomEvent("capabilities",
- { detail: { capabilities: this._capabilities } }));
- },
+ { detail: { capabilities: this._capabilities } }));
+ }
- _handle_message: function () {
+ _handle_message() {
if (this._sock.rQlen() === 0) {
Log.Warn("handle_message called on an empty receive queue");
return;
this._init_msg();
break;
}
- },
+ }
- _handleKeyEvent: function (keysym, code, down) {
+ _handleKeyEvent(keysym, code, down) {
this.sendKey(keysym, code, down);
- },
+ }
- _handleMouseButton: function (x, y, down, bmask) {
+ _handleMouseButton(x, y, down, bmask) {
if (down) {
this._mouse_buttonMask |= bmask;
} else {
if (this._rfb_connection_state !== 'connected') { return; }
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
- },
+ }
- _handleMouseMove: function (x, y) {
+ _handleMouseMove(x, y) {
if (this._viewportDragging) {
const deltaX = this._viewportDragPos.x - x;
const deltaY = this._viewportDragPos.y - y;
if (this._rfb_connection_state !== 'connected') { return; }
RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask);
- },
+ }
// Message Handlers
- _negotiate_protocol_version: function () {
+ _negotiate_protocol_version() {
if (this._sock.rQlen() < 12) {
return this._fail("Received incomplete protocol version.");
}
Log.Debug('Sent ProtocolVersion: ' + cversion);
this._rfb_init_state = 'Security';
- },
+ }
- _negotiate_security: function () {
+ _negotiate_security() {
// Polyfill since IE and PhantomJS doesn't have
// TypedArray.includes()
function includes(item, array) {
Log.Debug('Authenticating using scheme: ' + this._rfb_auth_scheme);
return this._init_msg(); // jump to authentication
- },
+ }
/*
* Get the security failure reason if sent from the server and
* - The optional parameter security_result_status can be used to
* add a custom status code to the event.
*/
- _handle_security_failure: function (context, security_result_status) {
+ _handle_security_failure(context, security_result_status) {
if (typeof context === 'undefined') {
context = "";
return this._fail("Security negotiation failed" + context);
}
- },
+ }
// authentication
- _negotiate_xvp_auth: function () {
+ _negotiate_xvp_auth() {
if (!this._rfb_credentials.username ||
!this._rfb_credentials.password ||
!this._rfb_credentials.target) {
this.dispatchEvent(new CustomEvent(
"credentialsrequired",
{ detail: { types: ["username", "password", "target"] } }));
- return false;
+ return false;
}
const xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) +
this._sock.send_string(xvp_auth_str);
this._rfb_auth_scheme = 2;
return this._negotiate_authentication();
- },
+ }
- _negotiate_std_vnc_auth: function () {
+ _negotiate_std_vnc_auth() {
if (this._sock.rQwait("auth challenge", 16)) { return false; }
if (!this._rfb_credentials.password) {
this._sock.send(response);
this._rfb_init_state = "SecurityResult";
return true;
- },
+ }
- _negotiate_tight_tunnels: function (numTunnels) {
+ _negotiate_tight_tunnels(numTunnels) {
const clientSupportedTunnelTypes = {
0: { vendor: 'TGHT', signature: 'NOTUNNEL' }
};
return this._fail("Server wanted tunnels, but doesn't support " +
"the notunnel type");
}
- },
+ }
- _negotiate_tight_auth: function () {
+ _negotiate_tight_auth() {
if (!this._rfb_tightvnc) { // first pass, do the tunnel negotiation
if (this._sock.rQwait("num tunnels", 4)) { return false; }
const numTunnels = this._sock.rQshift32();
}
return this._fail("No supported sub-auth types!");
- },
+ }
- _negotiate_authentication: function () {
+ _negotiate_authentication() {
switch (this._rfb_auth_scheme) {
case 0: // connection failed
return this._handle_security_failure("authentication scheme");
return this._fail("Unsupported auth scheme (scheme: " +
this._rfb_auth_scheme + ")");
}
- },
+ }
- _handle_security_result: function () {
+ _handle_security_result() {
if (this._sock.rQwait('VNC auth response ', 4)) { return false; }
const status = this._sock.rQshift32();
return this._fail("Security handshake failed");
}
}
- },
+ }
- _negotiate_server_init: function () {
+ _negotiate_server_init() {
if (this._sock.rQwait("server initialization", 24)) { return false; }
/* Screen size */
this._updateConnectionState('connected');
return true;
- },
+ }
- _sendEncodings: function () {
+ _sendEncodings() {
const encs = [];
// In preference order
}
RFB.messages.clientEncodings(this._sock, encs);
- },
+ }
/* RFB protocol initialization states:
* ProtocolVersion
* ClientInitialization - not triggered by server message
* ServerInitialization
*/
- _init_msg: function () {
+ _init_msg() {
switch (this._rfb_init_state) {
case 'ProtocolVersion':
return this._negotiate_protocol_version();
return this._fail("Unknown init state (state: " +
this._rfb_init_state + ")");
}
- },
+ }
- _handle_set_colour_map_msg: function () {
+ _handle_set_colour_map_msg() {
Log.Debug("SetColorMapEntries");
return this._fail("Unexpected SetColorMapEntries message");
- },
+ }
- _handle_server_cut_text: function () {
+ _handle_server_cut_text() {
Log.Debug("ServerCutText");
if (this._sock.rQwait("ServerCutText header", 7, 1)) { return false; }
{ detail: { text: text } }));
return true;
- },
+ }
- _handle_server_fence_msg: function() {
+ _handle_server_fence_msg() {
if (this._sock.rQwait("ServerFence header", 8, 1)) { return false; }
this._sock.rQskipBytes(3); // Padding
let flags = this._sock.rQshift32();
RFB.messages.clientFence(this._sock, flags, payload);
return true;
- },
+ }
- _handle_xvp_msg: function () {
+ _handle_xvp_msg() {
if (this._sock.rQwait("XVP version and message", 3, 1)) { return false; }
this._sock.rQskip8(); // Padding
const xvp_ver = this._sock.rQshift8();
}
return true;
- },
+ }
- _normal_msg: function () {
+ _normal_msg() {
let msg_type;
if (this._FBU.rects > 0) {
msg_type = 0;
Log.Debug("sock.rQslice(0, 30): " + this._sock.rQslice(0, 30));
return true;
}
- },
+ }
- _onFlush: function() {
+ _onFlush() {
this._flushing = false;
// Resume processing
if (this._sock.rQlen() > 0) {
this._handle_message();
}
- },
+ }
- _framebufferUpdate: function () {
+ _framebufferUpdate() {
if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; }
this._sock.rQskip8(); // Padding
this._display.flip();
return true; // We finished this FBU
- },
+ }
- _updateContinuousUpdates: function() {
+ _updateContinuousUpdates() {
if (!this._enabledContinuousUpdates) { return; }
RFB.messages.enableContinuousUpdates(this._sock, true, 0, 0,
this._fb_width, this._fb_height);
- },
+ }
- _resize: function(width, height) {
+ _resize(width, height) {
this._fb_width = width;
this._fb_height = height;
this._timing.fbu_rt_start = (new Date()).getTime();
this._updateContinuousUpdates();
- },
+ }
- _xvpOp: function (ver, op) {
+ _xvpOp(ver, op) {
if (this._rfb_xvp_ver < ver) { return; }
Log.Info("Sending XVP operation " + op + " (version " + ver + ")");
RFB.messages.xvpOp(this._sock, ver, op);
- },
-};
+ }
-Object.assign(RFB.prototype, EventTargetMixin);
+ static genDES(password, challenge) {
+ const passwd = [];
+ for (let i = 0; i < password.length; i++) {
+ passwd.push(password.charCodeAt(i));
+ }
+ return (new DES(passwd)).encrypt(challenge);
+ }
+}
// Class Methods
RFB.messages = {
- keyEvent: function (sock, keysym, down) {
+ keyEvent(sock, keysym, down) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- QEMUExtendedKeyEvent: function (sock, keysym, down, keycode) {
+ QEMUExtendedKeyEvent(sock, keysym, down, keycode) {
function getRFBkeycode(xt_scancode) {
const upperByte = (keycode >> 8);
const lowerByte = (keycode & 0x00ff);
sock.flush();
},
- pointerEvent: function (sock, x, y, mask) {
+ pointerEvent(sock, x, y, mask) {
const buff = sock._sQ;
const offset = sock._sQlen;
},
// TODO(directxman12): make this unicode compatible?
- clientCutText: function (sock, text) {
+ clientCutText(sock, text) {
const buff = sock._sQ;
const offset = sock._sQlen;
buff[offset + 2] = 0; // padding
buff[offset + 3] = 0; // padding
- const length = text.length;
+ let length = text.length;
buff[offset + 4] = length >> 24;
buff[offset + 5] = length >> 16;
let remaining = length;
while (remaining > 0) {
- const flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
+ let flushSize = Math.min(remaining, (sock._sQbufferSize - sock._sQlen));
if (flushSize <= 0) {
this._fail("Clipboard contents could not be sent");
break;
}
},
- setDesktopSize: function (sock, width, height, id, flags) {
+ setDesktopSize(sock, width, height, id, flags) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- clientFence: function (sock, flags, payload) {
+ clientFence(sock, flags, payload) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- enableContinuousUpdates: function (sock, enable, x, y, width, height) {
+ enableContinuousUpdates(sock, enable, x, y, width, height) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- pixelFormat: function (sock, depth, true_color) {
+ pixelFormat(sock, depth, true_color) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- clientEncodings: function (sock, encodings) {
+ clientEncodings(sock, encodings) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- fbUpdateRequest: function (sock, incremental, x, y, w, h) {
+ fbUpdateRequest(sock, incremental, x, y, w, h) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock.flush();
},
- xvpOp: function (sock, ver, op) {
+ xvpOp(sock, ver, op) {
const buff = sock._sQ;
const offset = sock._sQlen;
sock._sQlen += 4;
sock.flush();
- },
-};
-
-RFB.genDES = function (password, challenge) {
- const passwd = [];
- for (let i = 0; i < password.length; i++) {
- passwd.push(password.charCodeAt(i));
}
- return (new DES(passwd)).encrypt(challenge);
};
+
RFB.encodingHandlers = {
- RAW: function () {
+ RAW() {
if (this._FBU.lines === 0) {
this._FBU.lines = this._FBU.height;
}
return true;
},
- COPYRECT: function () {
+ COPYRECT() {
this._FBU.bytes = 4;
if (this._sock.rQwait("COPYRECT", 4)) { return false; }
this._display.copyImage(this._sock.rQshift16(), this._sock.rQshift16(),
return true;
},
- RRE: function () {
+ RRE() {
let color;
if (this._FBU.subrects === 0) {
this._FBU.bytes = 4 + 4;
return true;
},
- HEXTILE: function () {
+ HEXTILE() {
const rQ = this._sock.get_rQ();
let rQi = this._sock.get_rQi();
return true;
},
- TIGHT: function (isTightPNG) {
+ TIGHT(isTightPNG) {
this._FBU.bytes = 1; // compression-control byte
if (this._sock.rQwait("TIGHT compression-control", this._FBU.bytes)) { return false; }
return true;
},
- last_rect: function () {
+ last_rect() {
this._FBU.rects = 0;
return true;
},
- ExtendedDesktopSize: function () {
+ ExtendedDesktopSize() {
this._FBU.bytes = 1;
if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
return true;
},
- DesktopSize: function () {
+ DesktopSize() {
this._resize(this._FBU.width, this._FBU.height);
this._FBU.bytes = 0;
this._FBU.rects -= 1;
return true;
},
- Cursor: function () {
+ Cursor() {
Log.Debug(">> set_cursor");
const x = this._FBU.x; // hotspot-x
const y = this._FBU.y; // hotspot-y
return true;
},
- QEMUExtendedKeyEvent: function () {
+ QEMUExtendedKeyEvent() {
this._FBU.rects--;
// Old Safari doesn't support creating keyboard events
} catch (err) {
// Do nothing
}
- },
+ }
}
const useFallback = !supportsCursorURIs() || isTouchDevice;
-function Cursor(container) {
- this._target = null;
-
- this._canvas = document.createElement('canvas');
-
- if (useFallback) {
- this._canvas.style.position = 'fixed';
- this._canvas.style.zIndex = '65535';
- this._canvas.style.pointerEvents = 'none';
- // Can't use "display" because of Firefox bug #1445997
- this._canvas.style.visibility = 'hidden';
- document.body.appendChild(this._canvas);
- }
-
- this._position = { x: 0, y: 0 };
- this._hotSpot = { x: 0, y: 0 };
-
- this._eventHandlers = {
- 'mouseover': this._handleMouseOver.bind(this),
- 'mouseleave': this._handleMouseLeave.bind(this),
- 'mousemove': this._handleMouseMove.bind(this),
- 'mouseup': this._handleMouseUp.bind(this),
- 'touchstart': this._handleTouchStart.bind(this),
- 'touchmove': this._handleTouchMove.bind(this),
- 'touchend': this._handleTouchEnd.bind(this),
- };
-}
+export default class Cursor {
+ constructor(container) {
+ this._target = null;
+
+ this._canvas = document.createElement('canvas');
+
+ if (useFallback) {
+ this._canvas.style.position = 'fixed';
+ this._canvas.style.zIndex = '65535';
+ this._canvas.style.pointerEvents = 'none';
+ // Can't use "display" because of Firefox bug #1445997
+ this._canvas.style.visibility = 'hidden';
+ document.body.appendChild(this._canvas);
+ }
-Cursor.prototype = {
- attach: function (target) {
+ this._position = { x: 0, y: 0 };
+ this._hotSpot = { x: 0, y: 0 };
+
+ this._eventHandlers = {
+ 'mouseover': this._handleMouseOver.bind(this),
+ 'mouseleave': this._handleMouseLeave.bind(this),
+ 'mousemove': this._handleMouseMove.bind(this),
+ 'mouseup': this._handleMouseUp.bind(this),
+ 'touchstart': this._handleTouchStart.bind(this),
+ 'touchmove': this._handleTouchMove.bind(this),
+ 'touchend': this._handleTouchEnd.bind(this),
+ };
+ }
+
+ attach(target) {
if (this._target) {
this.detach();
}
}
this.clear();
- },
+ }
- detach: function () {
+ detach() {
if (useFallback) {
const options = { capture: true, passive: true };
this._target.removeEventListener('mouseover', this._eventHandlers.mouseover, options);
}
this._target = null;
- },
+ }
- change: function (pixels, mask, hotx, hoty, w, h) {
+ change(pixels, mask, hotx, hoty, w, h) {
if ((w === 0) || (h === 0)) {
this.clear();
return;
let url = this._canvas.toDataURL();
this._target.style.cursor = 'url(' + url + ')' + hotx + ' ' + hoty + ', default';
}
- },
+ }
- clear: function () {
+ clear() {
this._target.style.cursor = 'none';
this._canvas.width = 0;
this._canvas.height = 0;
this._position.y = this._position.y + this._hotSpot.y;
this._hotSpot.x = 0;
this._hotSpot.y = 0;
- },
+ }
- _handleMouseOver: function (event) {
+ _handleMouseOver(event) {
// This event could be because we're entering the target, or
// moving around amongst its sub elements. Let the move handler
// sort things out.
this._handleMouseMove(event);
- },
+ }
- _handleMouseLeave: function (event) {
+ _handleMouseLeave(event) {
this._hideCursor();
- },
+ }
- _handleMouseMove: function (event) {
+ _handleMouseMove(event) {
this._updateVisibility(event.target);
this._position.x = event.clientX - this._hotSpot.x;
this._position.y = event.clientY - this._hotSpot.y;
this._updatePosition();
- },
+ }
- _handleMouseUp: function (event) {
+ _handleMouseUp(event) {
// We might get this event because of a drag operation that
// moved outside of the target. Check what's under the cursor
// now and adjust visibility based on that.
let target = document.elementFromPoint(event.clientX, event.clientY);
this._updateVisibility(target);
- },
+ }
- _handleTouchStart: function (event) {
+ _handleTouchStart(event) {
// Just as for mouseover, we let the move handler deal with it
this._handleTouchMove(event);
- },
+ }
- _handleTouchMove: function (event) {
+ _handleTouchMove(event) {
this._updateVisibility(event.target);
this._position.x = event.changedTouches[0].clientX - this._hotSpot.x;
this._position.y = event.changedTouches[0].clientY - this._hotSpot.y;
this._updatePosition();
- },
+ }
- _handleTouchEnd: function (event) {
+ _handleTouchEnd(event) {
// Same principle as for mouseup
let target = document.elementFromPoint(event.changedTouches[0].clientX,
event.changedTouches[0].clientY);
this._updateVisibility(target);
- },
+ }
- _showCursor: function () {
+ _showCursor() {
if (this._canvas.style.visibility === 'hidden')
this._canvas.style.visibility = '';
- },
+ }
- _hideCursor: function () {
+ _hideCursor() {
if (this._canvas.style.visibility !== 'hidden')
this._canvas.style.visibility = 'hidden';
- },
+ }
// Should we currently display the cursor?
// (i.e. are we over the target, or a child of the target without a
// different cursor set)
- _shouldShowCursor: function (target) {
+ _shouldShowCursor(target) {
// Easy case
if (target === this._target)
return true;
if (window.getComputedStyle(target).cursor !== 'none')
return false;
return true;
- },
+ }
- _updateVisibility: function (target) {
+ _updateVisibility(target) {
if (this._shouldShowCursor(target))
this._showCursor();
else
this._hideCursor();
- },
+ }
- _updatePosition: function () {
+ _updatePosition() {
this._canvas.style.left = this._position.x + "px";
this._canvas.style.top = this._position.y + "px";
- },
-};
-
-export default Cursor;
+ }
+}
* See README.md for usage and integration instructions.
*/
-const EventTargetMixin = {
- _listeners: null,
+export default class EventTargetMixin {
+ constructor() {
+ this._listeners = null;
+ }
- addEventListener: function(type, callback) {
+ addEventListener(type, callback) {
if (!this._listeners) {
this._listeners = new Map();
}
this._listeners.set(type, new Set());
}
this._listeners.get(type).add(callback);
- },
+ }
- removeEventListener: function(type, callback) {
+ removeEventListener(type, callback) {
if (!this._listeners || !this._listeners.has(type)) {
return;
}
this._listeners.get(type).delete(callback);
- },
+ }
- dispatchEvent: function(event) {
+ dispatchEvent(event) {
if (!this._listeners || !this._listeners.has(event.type)) {
return true;
}
callback.call(this, event);
}, this);
return !event.defaultPrevented;
- },
-};
-
-export default EventTargetMixin;
+ }
+}
* Decode from UTF-8
*/
export function decodeUTF8 (utf8string) {
- "use strict";
return decodeURIComponent(escape(utf8string));
}
import * as Log from './util/logging.js';
-export default function Websock() {
- "use strict";
-
- this._websocket = null; // WebSocket object
-
- this._rQi = 0; // Receive queue index
- this._rQlen = 0; // Next write position in the receive queue
- this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
- this._rQmax = this._rQbufferSize / 8;
- // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
- this._rQ = null; // Receive queue
-
- this._sQbufferSize = 1024 * 10; // 10 KiB
- // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
- this._sQlen = 0;
- this._sQ = null; // Send queue
-
- this._eventHandlers = {
- 'message': function () {},
- 'open': function () {},
- 'close': function () {},
- 'error': function () {}
- };
-}
-
// this has performance issues in some versions Chromium, and
// doesn't gain a tremendous amount of performance increase in Firefox
// at the moment. It may be valuable to turn it on in the future.
const ENABLE_COPYWITHIN = false;
-
const MAX_RQ_GROW_SIZE = 40 * 1024 * 1024; // 40 MiB
-Websock.prototype = {
+export default class Websock {
+ constructor() {
+ this._websocket = null; // WebSocket object
+
+ this._rQi = 0; // Receive queue index
+ this._rQlen = 0; // Next write position in the receive queue
+ this._rQbufferSize = 1024 * 1024 * 4; // Receive queue buffer size (4 MiB)
+ this._rQmax = this._rQbufferSize / 8;
+ // called in init: this._rQ = new Uint8Array(this._rQbufferSize);
+ this._rQ = null; // Receive queue
+
+ this._sQbufferSize = 1024 * 10; // 10 KiB
+ // called in init: this._sQ = new Uint8Array(this._sQbufferSize);
+ this._sQlen = 0;
+ this._sQ = null; // Send queue
+
+ this._eventHandlers = {
+ message: () => {},
+ open: () => {},
+ close: () => {},
+ error: () => {}
+ };
+ }
+
// Getters and Setters
- get_sQ: function () {
+ get_sQ() {
return this._sQ;
- },
+ }
- get_rQ: function () {
+ get_rQ() {
return this._rQ;
- },
+ }
- get_rQi: function () {
+ get_rQi() {
return this._rQi;
- },
+ }
- set_rQi: function (val) {
+ set_rQi(val) {
this._rQi = val;
- },
+ }
// Receive Queue
- rQlen: function () {
+ rQlen() {
return this._rQlen - this._rQi;
- },
+ }
- rQpeek8: function () {
+ rQpeek8() {
return this._rQ[this._rQi];
- },
+ }
- rQshift8: function () {
+ rQshift8() {
return this._rQ[this._rQi++];
- },
+ }
- rQskip8: function () {
+ rQskip8() {
this._rQi++;
- },
+ }
- rQskipBytes: function (num) {
+ rQskipBytes(num) {
this._rQi += num;
- },
+ }
// TODO(directxman12): test performance with these vs a DataView
- rQshift16: function () {
+ rQshift16() {
return (this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
- },
+ }
- rQshift32: function () {
+ rQshift32() {
return (this._rQ[this._rQi++] << 24) +
(this._rQ[this._rQi++] << 16) +
(this._rQ[this._rQi++] << 8) +
this._rQ[this._rQi++];
- },
+ }
- rQshiftStr: function (len) {
+ rQshiftStr(len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
let str = "";
// Handle large arrays in steps to avoid long strings on the stack
str += String.fromCharCode.apply(null, part);
}
return str;
- },
+ }
- rQshiftBytes: function (len) {
+ rQshiftBytes(len) {
if (typeof(len) === 'undefined') { len = this.rQlen(); }
this._rQi += len;
return new Uint8Array(this._rQ.buffer, this._rQi - len, len);
- },
+ }
- rQshiftTo: function (target, len) {
+ rQshiftTo(target, len) {
if (len === undefined) { len = this.rQlen(); }
// TODO: make this just use set with views when using a ArrayBuffer to store the rQ
target.set(new Uint8Array(this._rQ.buffer, this._rQi, len));
this._rQi += len;
- },
+ }
- rQwhole: function () {
+ rQwhole() {
return new Uint8Array(this._rQ.buffer, 0, this._rQlen);
- },
+ }
- rQslice: function (start, end) {
+ rQslice(start, end) {
if (end) {
return new Uint8Array(this._rQ.buffer, this._rQi + start, end - start);
} else {
return new Uint8Array(this._rQ.buffer, this._rQi + start, this._rQlen - this._rQi - start);
}
- },
+ }
// Check to see if we must wait for 'num' bytes (default to FBU.bytes)
// to be available in the receive queue. Return true if we need to
// wait (and possibly print a debug message), otherwise false.
- rQwait: function (msg, num, goback) {
+ rQwait(msg, num, goback) {
const rQlen = this._rQlen - this._rQi; // Skip rQlen() function call
if (rQlen < num) {
if (goback) {
return true; // true means need more data
}
return false;
- },
+ }
// Send Queue
- flush: function () {
+ flush() {
if (this._sQlen > 0 && this._websocket.readyState === WebSocket.OPEN) {
this._websocket.send(this._encode_message());
this._sQlen = 0;
}
- },
+ }
- send: function (arr) {
+ send(arr) {
this._sQ.set(arr, this._sQlen);
this._sQlen += arr.length;
this.flush();
- },
+ }
- send_string: function (str) {
+ send_string(str) {
this.send(str.split('').map(function (chr) {
return chr.charCodeAt(0);
}));
- },
+ }
// Event Handlers
- off: function (evt) {
+ off(evt) {
this._eventHandlers[evt] = function () {};
- },
+ }
- on: function (evt, handler) {
+ on(evt, handler) {
this._eventHandlers[evt] = handler;
- },
+ }
- _allocate_buffers: function () {
+ _allocate_buffers() {
this._rQ = new Uint8Array(this._rQbufferSize);
this._sQ = new Uint8Array(this._sQbufferSize);
- },
+ }
- init: function () {
+ init() {
this._allocate_buffers();
this._rQi = 0;
this._websocket = null;
- },
+ }
- open: function (uri, protocols) {
+ open(uri, protocols) {
this.init();
this._websocket = new WebSocket(uri, protocols);
this._eventHandlers.error(e);
Log.Debug("<< WebSock.onerror: " + e);
}).bind(this);
- },
+ }
- close: function () {
+ close() {
if (this._websocket) {
if ((this._websocket.readyState === WebSocket.OPEN) ||
(this._websocket.readyState === WebSocket.CONNECTING)) {
this._websocket.onmessage = function (e) { return; };
}
- },
+ }
// private methods
- _encode_message: function () {
+ _encode_message() {
// Put in a binary arraybuffer
// according to the spec, you can send ArrayBufferViews with the send method
return new Uint8Array(this._sQ.buffer, 0, this._sQlen);
- },
+ }
- _expand_compact_rQ: function (min_fit) {
+ _expand_compact_rQ(min_fit) {
const resizeNeeded = min_fit || this._rQlen - this._rQi > this._rQbufferSize / 2;
if (resizeNeeded) {
if (!min_fit) {
this._rQlen = this._rQlen - this._rQi;
this._rQi = 0;
- },
+ }
- _decode_message: function (data) {
+ _decode_message(data) {
// push arraybuffer values onto the end
const u8 = new Uint8Array(data);
if (u8.length > this._rQbufferSize - this._rQlen) {
}
this._rQ.set(u8, this._rQlen);
this._rQlen += u8.length;
- },
+ }
- _recv_message: function (e) {
+ _recv_message(e) {
this._decode_message(e.data);
if (this.rQlen() > 0) {
this._eventHandlers.message();
Log.Debug("Ignoring empty message");
}
}
-};
+}
{
- "env": {
- "node": true,
- "mocha": true
- },
- "globals": {
- "chai": true
- }
+ "env": {
+ "node": true,
+ "mocha": true
+ },
+ "globals": {
+ "chai": true
+ }
}
\ No newline at end of file
return evt;
}
-export default function FakeWebSocket (uri, protocols) {
- this.url = uri;
- this.binaryType = "arraybuffer";
- this.extensions = "";
+export default class FakeWebSocket {
+ constructor(uri, protocols) {
+ this.url = uri;
+ this.binaryType = "arraybuffer";
+ this.extensions = "";
- if (!protocols || typeof protocols === 'string') {
- this.protocol = protocols;
- } else {
- this.protocol = protocols[0];
- }
+ if (!protocols || typeof protocols === 'string') {
+ this.protocol = protocols;
+ } else {
+ this.protocol = protocols[0];
+ }
- this._send_queue = new Uint8Array(20000);
+ this._send_queue = new Uint8Array(20000);
- this.readyState = FakeWebSocket.CONNECTING;
- this.bufferedAmount = 0;
+ this.readyState = FakeWebSocket.CONNECTING;
+ this.bufferedAmount = 0;
- this.__is_fake = true;
-}
+ this.__is_fake = true;
+ }
-FakeWebSocket.prototype = {
- close: function (code, reason) {
+ close(code, reason) {
this.readyState = FakeWebSocket.CLOSED;
if (this.onclose) {
this.onclose(make_event("close", { 'code': code, 'reason': reason, 'wasClean': true }));
}
- },
+ }
- send: function (data) {
+ send(data) {
if (this.protocol == 'base64') {
data = Base64.decode(data);
} else {
}
this._send_queue.set(data, this.bufferedAmount);
this.bufferedAmount += data.length;
- },
+ }
- _get_sent_data: function () {
+ _get_sent_data() {
const res = new Uint8Array(this._send_queue.buffer, 0, this.bufferedAmount);
this.bufferedAmount = 0;
return res;
- },
+ }
- _open: function (data) {
+ _open() {
this.readyState = FakeWebSocket.OPEN;
if (this.onopen) {
this.onopen(make_event('open'));
}
- },
+ }
- _receive_data: function (data) {
+ _receive_data(data) {
this.onmessage(make_event("message", { 'data': data }));
}
-};
+}
FakeWebSocket.OPEN = WebSocket.OPEN;
FakeWebSocket.CONNECTING = WebSocket.CONNECTING;
encoding = VNC_frame_encoding;
}
-function IterationPlayer (iterations, frames, encoding) {
- this._iterations = iterations;
+class IterationPlayer {
+ constructor(iterations, frames, encoding) {
+ this._iterations = iterations;
- this._iteration = undefined;
- this._player = undefined;
+ this._iteration = undefined;
+ this._player = undefined;
- this._start_time = undefined;
+ this._start_time = undefined;
- this._frames = frames;
- this._encoding = encoding;
+ this._frames = frames;
+ this._encoding = encoding;
- this._state = 'running';
+ this._state = 'running';
this.onfinish = function() {};
this.oniterationfinish = function() {};
this.rfbdisconnected = function() {};
-}
+ }
-IterationPlayer.prototype = {
- start: function (mode) {
+ start(mode) {
this._iteration = 0;
this._start_time = (new Date()).getTime();
this._trafficMgmt = !mode.endsWith('-no-mgmt');
this._nextIteration();
- },
+ }
- _nextIteration: function () {
+ _nextIteration() {
const player = new RecordingPlayer(this._frames, this._encoding, this._disconnected.bind(this));
player.onfinish = this._iterationFinish.bind(this);
}
player.run(this._realtime, this._trafficMgmt);
- },
+ }
- _finish: function () {
+ _finish() {
const endTime = (new Date()).getTime();
const totalDuration = endTime - this._start_time;
evt.duration = totalDuration;
evt.iterations = this._iterations;
this.onfinish(evt);
- },
+ }
- _iterationFinish: function (duration) {
+ _iterationFinish(duration) {
const evt = new Event('iterationfinish');
evt.duration = duration;
evt.number = this._iteration;
this.oniterationfinish(evt);
this._nextIteration();
- },
+ }
- _disconnected: function (clean, frame) {
+ _disconnected(clean, frame) {
if (!clean) {
this._state = 'failed';
}
evt.iteration = this._iteration;
this.onrfbdisconnected(evt);
- },
-};
+ }
+}
function start() {
document.getElementById('startButton').value = "Running";
window.addEventListener("message", _onMessage);
}
-export default function RecordingPlayer (frames, encoding, disconnected) {
- this._frames = frames;
- this._encoding = encoding;
+export default class RecordingPlayer {
+ constructor(frames, encoding, disconnected) {
+ this._frames = frames;
+ this._encoding = encoding;
- this._disconnected = disconnected;
+ this._disconnected = disconnected;
- if (this._encoding === undefined) {
+ if (this._encoding === undefined) {
const frame = this._frames[0];
const start = frame.indexOf('{', 1) + 1;
- if (frame.slice(start).startsWith('UkZC')) {
- this._encoding = 'base64';
- } else {
- this._encoding = 'binary';
+ if (frame.slice(start).startsWith('UkZC')) {
+ this._encoding = 'base64';
+ } else {
+ this._encoding = 'binary';
+ }
}
- }
- this._rfb = undefined;
- this._frame_length = this._frames.length;
+ this._rfb = undefined;
+ this._frame_length = this._frames.length;
- this._frame_index = 0;
- this._start_time = undefined;
- this._realtime = true;
- this._trafficManagement = true;
+ this._frame_index = 0;
+ this._start_time = undefined;
+ this._realtime = true;
+ this._trafficManagement = true;
- this._running = false;
+ this._running = false;
this.onfinish = function () {};
-}
+ }
-RecordingPlayer.prototype = {
- run: function (realtime, trafficManagement) {
+ run(realtime, trafficManagement) {
// initialize a new RFB
this._rfb = new RFB(document.getElementById('VNC_screen'), 'wss://test');
this._rfb.viewOnly = true;
this._running = true;
this._queueNextPacket();
- },
+ }
// _enablePlaybackMode mocks out things not required for running playback
- _enablePlaybackMode: function () {
+ _enablePlaybackMode() {
this._rfb._sock.send = function (arr) {};
this._rfb._sock.close = function () {};
this._rfb._sock.flush = function () {};
this.init();
this._eventHandlers.open();
};
- },
+ }
- _queueNextPacket: function () {
+ _queueNextPacket() {
if (!this._running) { return; }
let frame = this._frames[this._frame_index];
} else {
setImmediate(this._doPacket.bind(this));
}
- },
+ }
- _doPacket: function () {
+ _doPacket() {
// Avoid having excessive queue buildup in non-realtime mode
if (this._trafficManagement && this._rfb._flushing) {
const player = this;
this._frame_index++;
this._queueNextPacket();
- },
+ }
_finish() {
if (this._rfb._display.pending()) {
delete this._rfb;
this.onfinish((new Date()).getTime() - this._start_time);
}
- },
+ }
_handleDisconnect(evt) {
this._running = false;
this._disconnected(evt.detail.clean, this._frame_index);
}
-};
+}
describe('#sendCtrlAlDel', function () {
it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () {
- const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}};
+ const expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: () => {}};
RFB.messages.keyEvent(expected, 0xFFE3, 1);
RFB.messages.keyEvent(expected, 0xFFE9, 1);
RFB.messages.keyEvent(expected, 0xFFFF, 1);
describe('#sendKey', function () {
it('should send a single key with the given code and state (down = true)', function () {
- const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
+ const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
RFB.messages.keyEvent(expected, 123, 1);
client.sendKey(123, 'Key123', true);
expect(client._sock).to.have.sent(expected._sQ);
});
it('should send both a down and up event if the state is not specified', function () {
- const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
+ const expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
RFB.messages.keyEvent(expected, 123, 1);
RFB.messages.keyEvent(expected, 123, 0);
client.sendKey(123, 'Key123');
it('should send QEMU extended events if supported', function () {
client._qemuExtKeyEventSupported = true;
- const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
+ const expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
client.sendKey(0x20, 'Space', true);
expect(client._sock).to.have.sent(expected._sQ);
it('should not send QEMU extended events if unknown key code', function () {
client._qemuExtKeyEventSupported = true;
- const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
+ const expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
RFB.messages.keyEvent(expected, 123, 1);
client.sendKey(123, 'FooBar', true);
expect(client._sock).to.have.sent(expected._sQ);
describe('#clipboardPasteFrom', function () {
it('should send the given text in a paste event', function () {
const expected = {_sQ: new Uint8Array(11), _sQlen: 0,
- _sQbufferSize: 11, flush: function () {}};
+ _sQbufferSize: 11, flush: () => {}};
RFB.messages.clientCutText(expected, 'abc');
client.clipboardPasteFrom('abc');
expect(client._sock).to.have.sent(expected._sQ);
}
it('should send an update request if there is sufficient data', function () {
- const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
client._framebufferUpdate = function () { return true; };
});
it('should resume receiving an update if we previously did not have enough data', function () {
- const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
RFB.messages.fbUpdateRequest(expected_msg, true, 0, 0, 640, 20);
// just enough to set FBU.rects
});
it('should respond correctly to ServerFence', function () {
- const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
- const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function() {}};
+ const expected_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
+ const incoming_msg = {_sQ: new Uint8Array(16), _sQlen: 0, flush: () => {}};
const payload = "foo\x00ab9";
});
it('should enable continuous updates on first EndOfContinousUpdates', function () {
- const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 640, 20);
});
it('should update continuous updates on resize', function () {
- const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: function() {}};
+ const expected_msg = {_sQ: new Uint8Array(10), _sQlen: 0, flush: () => {}};
RFB.messages.enableContinuousUpdates(expected_msg, true, 0, 0, 90, 700);
client._resize(450, 160);
it('should send a pointer event on mouse button presses', function () {
client._handleMouseButton(10, 12, 1, 0x001);
- const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a mask of 1 on mousedown', function () {
client._handleMouseButton(10, 12, 1, 0x001);
- const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a mask of 0 on mouseup', function () {
client._mouse_buttonMask = 0x001;
client._handleMouseButton(10, 12, 0, 0x001);
- const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should send a pointer event on mouse movement', function () {
client._handleMouseMove(10, 12);
- const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}};
+ const pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x000);
expect(client._sock).to.have.sent(pointer_msg._sQ);
});
it('should set the button mask so that future mouse movements use it', function () {
client._handleMouseButton(10, 12, 1, 0x010);
client._handleMouseMove(13, 9);
- const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
+ const pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: () => {}};
RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010);
RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010);
expect(client._sock).to.have.sent(pointer_msg._sQ);
describe('Keyboard Event Handlers', function () {
it('should send a key message on a key press', function () {
client._handleKeyEvent(0x41, 'KeyA', true);
- const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
+ const key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: () => {}};
RFB.messages.keyEvent(key_msg, 0x41, 1);
expect(client._sock).to.have.sent(key_msg._sQ);
});
window.chrome = {
storage: {
sync: {
- get: function(cb){ cb(settings); },
- set: function(){},
- remove: function() {}
+ get(cb){ cb(settings); },
+ set(){},
+ remove() {}
}
}
};