diff --git a/app/styles/base.css b/app/styles/base.css index 790837a0..b8ce81bd 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -591,17 +591,17 @@ select:active { padding: 0 10px; } -/* XVP Shutdown/Reboot */ -:root:not(.noVNC_connected) #noVNC_xvp_button { +/* Shutdown/Reboot */ +:root:not(.noVNC_connected) #noVNC_power_button { display: none; } -#noVNC_xvp { +#noVNC_power { } -#noVNC_xvp_buttons { +#noVNC_power_buttons { display: none; } -#noVNC_xvp input[type=button] { +#noVNC_power input[type=button] { width: 100%; } diff --git a/app/ui.js b/app/ui.js index da912f33..f6615674 100644 --- a/app/ui.js +++ b/app/ui.js @@ -13,7 +13,7 @@ import * as Log from '../core/util/logging.js'; import _, { l10n } from '../core/util/localization.js'; -import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js'; +import { isTouchDevice } from '../core/util/browsers.js'; import { setCapture, getPointerEvent } from '../core/util/events.js'; import KeyTable from "../core/input/keysym.js"; import keysyms from "../core/input/keysymdef.js"; @@ -91,7 +91,7 @@ var UI = { UI.addControlbarHandlers(); UI.addTouchSpecificHandlers(); UI.addExtraKeysHandlers(); - UI.addXvpHandlers(); + UI.addMachineHandlers(); UI.addConnectionControlHandlers(); UI.addClipboardHandlers(); UI.addSettingsHandlers(); @@ -167,7 +167,6 @@ var UI = { UI.initSetting('host', window.location.hostname); UI.initSetting('port', port); UI.initSetting('encrypt', (window.location.protocol === "https:")); - UI.initSetting('cursor', !isTouchDevice); UI.initSetting('view_clip', false); UI.initSetting('resize', 'off'); UI.initSetting('shared', true); @@ -200,28 +199,6 @@ var UI = { } }, - initRFB: function() { - try { - UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'), - 'onNotification': UI.notification, - 'onUpdateState': UI.updateState, - 'onDisconnected': UI.disconnectFinished, - 'onPasswordRequired': UI.passwordRequired, - 'onXvpInit': UI.updateXvpButton, - 'onClipboard': UI.clipboardReceive, - 'onBell': UI.bell, - 'onFBUComplete': UI.initialResize, - 'onFBResize': UI.updateSessionSize, - 'onDesktopName': UI.updateDesktopName}); - return true; - } catch (exc) { - var msg = "Unable to create RFB client -- " + exc; - Log.Error(msg); - UI.showStatus(msg, 'error'); - return false; - } - }, - /* ------^------- * /INIT * ============== @@ -278,8 +255,8 @@ var UI = { document.getElementById("noVNC_keyboard_button") .addEventListener('click', UI.toggleVirtualKeyboard); - UI.touchKeyboard = new Keyboard({target: document.getElementById('noVNC_keyboardinput'), - onKeyEvent: UI.keyEvent}); + UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput')); + UI.touchKeyboard.onkeyevent = UI.keyEvent; UI.touchKeyboard.grab(); document.getElementById("noVNC_keyboardinput") .addEventListener('input', UI.keyInput); @@ -330,15 +307,15 @@ var UI = { .addEventListener('click', UI.sendCtrlAltDel); }, - addXvpHandlers: function() { - document.getElementById("noVNC_xvp_shutdown_button") - .addEventListener('click', function() { UI.rfb.xvpShutdown(); }); - document.getElementById("noVNC_xvp_reboot_button") - .addEventListener('click', function() { UI.rfb.xvpReboot(); }); - document.getElementById("noVNC_xvp_reset_button") - .addEventListener('click', function() { UI.rfb.xvpReset(); }); - document.getElementById("noVNC_xvp_button") - .addEventListener('click', UI.toggleXvpPanel); + addMachineHandlers: function() { + document.getElementById("noVNC_shutdown_button") + .addEventListener('click', function() { UI.rfb.machineShutdown(); }); + document.getElementById("noVNC_reboot_button") + .addEventListener('click', function() { UI.rfb.machineReboot(); }); + document.getElementById("noVNC_reset_button") + .addEventListener('click', function() { UI.rfb.machineReset(); }); + document.getElementById("noVNC_power_button") + .addEventListener('click', UI.togglePowerPanel); }, addConnectionControlHandlers: function() { @@ -377,8 +354,6 @@ var UI = { .addEventListener('click', UI.toggleSettingsPanel); UI.addSettingChangeHandler('encrypt'); - UI.addSettingChangeHandler('cursor'); - UI.addSettingChangeHandler('cursor', UI.updateLocalCursor); UI.addSettingChangeHandler('resize'); UI.addSettingChangeHandler('resize', UI.enableDisableViewClip); UI.addSettingChangeHandler('resize', UI.applyResizeMode); @@ -413,7 +388,7 @@ var UI = { * VISUAL * ------v------*/ - updateState: function(rfb, state, oldstate) { + updateState: function(event) { var msg; document.documentElement.classList.remove("noVNC_connecting"); @@ -421,7 +396,7 @@ var UI = { document.documentElement.classList.remove("noVNC_disconnecting"); document.documentElement.classList.remove("noVNC_reconnecting"); - switch (state) { + switch (event.detail.state) { case 'connecting': document.getElementById("noVNC_transition_text").textContent = _("Connecting..."); document.documentElement.classList.add("noVNC_connecting"); @@ -429,8 +404,9 @@ var UI = { case 'connected': UI.connected = true; UI.inhibit_reconnect = false; + UI.doneInitialResize = false; document.documentElement.classList.add("noVNC_connected"); - if (rfb && rfb.get_encrypt()) { + if (UI.getSetting('encrypt')) { msg = _("Connected (encrypted) to ") + UI.desktopName; } else { msg = _("Connected (unencrypted) to ") + UI.desktopName; @@ -462,12 +438,6 @@ var UI = { UI.enableDisableViewClip(); - if (cursorURIsSupported() && !isTouchDevice) { - UI.enableSetting('cursor'); - } else { - UI.disableSetting('cursor'); - } - if (UI.connected) { UI.disableSetting('encrypt'); UI.disableSetting('shared'); @@ -487,12 +457,12 @@ var UI = { UI.enableSetting('port'); UI.enableSetting('path'); UI.enableSetting('repeaterID'); - UI.updateXvpButton(0); + UI.updatePowerButton(); UI.keepControlbar(); } // Hide input related buttons in view only mode - if (UI.rfb && UI.rfb.get_view_only()) { + if (UI.rfb && UI.rfb.viewOnly) { document.getElementById('noVNC_keyboard_button') .classList.add('noVNC_hidden'); document.getElementById('noVNC_toggle_extra_keys_button') @@ -562,8 +532,8 @@ var UI = { document.getElementById('noVNC_status').classList.remove("noVNC_open"); }, - notification: function (rfb, msg, level, options) { - UI.showStatus(msg, level); + notification: function (e) { + UI.showStatus(e.detail.message, e.detail.level); }, activateControlbar: function(event) { @@ -866,7 +836,7 @@ var UI = { closeAllPanels: function() { UI.closeSettingsPanel(); - UI.closeXvpPanel(); + UI.closePowerPanel(); UI.closeClipboardPanel(); UI.closeExtraKeys(); }, @@ -883,12 +853,6 @@ var UI = { // Refresh UI elements from saved cookies UI.updateSetting('encrypt'); - if (cursorURIsSupported()) { - UI.updateSetting('cursor'); - } else { - UI.updateSetting('cursor', !isTouchDevice); - UI.disableSetting('cursor'); - } UI.updateSetting('view_clip'); UI.updateSetting('resize'); UI.updateSetting('shared'); @@ -924,50 +888,52 @@ var UI = { /* ------^------- * /SETTINGS * ============== - * XVP + * POWER * ------v------*/ - openXvpPanel: function() { + openPowerPanel: function() { UI.closeAllPanels(); UI.openControlbar(); - document.getElementById('noVNC_xvp') + document.getElementById('noVNC_power') .classList.add("noVNC_open"); - document.getElementById('noVNC_xvp_button') + document.getElementById('noVNC_power_button') .classList.add("noVNC_selected"); }, - closeXvpPanel: function() { - document.getElementById('noVNC_xvp') + closePowerPanel: function() { + document.getElementById('noVNC_power') .classList.remove("noVNC_open"); - document.getElementById('noVNC_xvp_button') + document.getElementById('noVNC_power_button') .classList.remove("noVNC_selected"); }, - toggleXvpPanel: function() { - if (document.getElementById('noVNC_xvp') + togglePowerPanel: function() { + if (document.getElementById('noVNC_power') .classList.contains("noVNC_open")) { - UI.closeXvpPanel(); + UI.closePowerPanel(); } else { - UI.openXvpPanel(); + UI.openPowerPanel(); } }, - // Disable/enable XVP button - updateXvpButton: function(ver) { - if (ver >= 1 && !UI.rfb.get_view_only()) { - document.getElementById('noVNC_xvp_button') + // Disable/enable power button + updatePowerButton: function() { + if (UI.connected && + UI.rfb.capabilities.power && + !UI.rfb.viewOnly) { + document.getElementById('noVNC_power_button') .classList.remove("noVNC_hidden"); } else { - document.getElementById('noVNC_xvp_button') + document.getElementById('noVNC_power_button') .classList.add("noVNC_hidden"); - // Close XVP panel if open - UI.closeXvpPanel(); + // Close power panel if open + UI.closePowerPanel(); } }, /* ------^------- - * /XVP + * /POWER * ============== * CLIPBOARD * ------v------*/ @@ -998,9 +964,9 @@ var UI = { } }, - clipboardReceive: function(rfb, text) { - Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "..."); - document.getElementById('noVNC_clipboard_text').value = text; + clipboardReceive: function(e) { + Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0,40) + "..."); + document.getElementById('noVNC_clipboard_text').value = e.detail.text; Log.Debug("<< UI.clipboardReceive"); }, @@ -1053,19 +1019,34 @@ var UI = { return; } - if (!UI.initRFB()) return; - UI.closeAllPanels(); UI.closeConnectPanel(); - UI.rfb.set_encrypt(UI.getSetting('encrypt')); - UI.rfb.set_shared(UI.getSetting('shared')); - UI.rfb.set_repeaterID(UI.getSetting('repeaterID')); - - UI.updateLocalCursor(); UI.updateViewOnly(); - UI.rfb.connect(host, port, password, path); + var url; + + url = UI.getSetting('encrypt') ? 'wss' : 'ws'; + + url += '://' + host; + if(port) { + url += ':' + port; + } + url += '/' + path; + + UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url, + { shared: UI.getSetting('shared'), + repeaterID: UI.getSetting('repeaterID'), + credentials: { password: password } }); + UI.rfb.addEventListener("notification", UI.notification); + UI.rfb.addEventListener("updatestate", UI.updateState); + UI.rfb.addEventListener("disconnect", UI.disconnectFinished); + UI.rfb.addEventListener("credentialsrequired", UI.credentials); + UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); }); + UI.rfb.addEventListener("clipboard", UI.clipboardReceive); + UI.rfb.addEventListener("bell", UI.bell); + UI.rfb.addEventListener("fbresize", UI.updateSessionSize); + UI.rfb.addEventListener("desktopname", UI.updateDesktopName); }, disconnect: function() { @@ -1075,9 +1056,6 @@ var UI = { // Disable automatic reconnecting UI.inhibit_reconnect = true; - // Restore the callback used for initial resize - UI.rfb.set_onFBUComplete(UI.initialResize); - // Don't display the connection settings until we're actually disconnected }, @@ -1092,9 +1070,9 @@ var UI = { UI.connect(null, UI.reconnect_password); }, - disconnectFinished: function (rfb, reason) { - if (typeof reason !== 'undefined') { - UI.showStatus(reason, 'error'); + disconnectFinished: function (e) { + if (typeof e.detail.reason !== 'undefined') { + UI.showStatus(e.detail.reason, 'error'); } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) { document.getElementById("noVNC_transition_text").textContent = _("Reconnecting..."); document.documentElement.classList.add("noVNC_reconnecting"); @@ -1125,8 +1103,8 @@ var UI = { * PASSWORD * ------v------*/ - passwordRequired: function(rfb, msg) { - + credentials: function(e) { + // FIXME: handle more types document.getElementById('noVNC_password_dlg') .classList.add('noVNC_open'); @@ -1134,24 +1112,23 @@ var UI = { document.getElementById('noVNC_password_input').focus(); }, 100); - if (typeof msg === 'undefined') { - msg = _("Password is required"); - } + var msg = _("Password is required"); Log.Warn(msg); UI.showStatus(msg, "warning"); }, setPassword: function(e) { + // Prevent actually submitting the form + e.preventDefault(); + var inputElem = document.getElementById('noVNC_password_input'); var password = inputElem.value; // Clear the input after reading the password inputElem.value = ""; - UI.rfb.sendPassword(password); + UI.rfb.sendCredentials({ password: password }); UI.reconnect_password = password; document.getElementById('noVNC_password_dlg') .classList.remove('noVNC_open'); - // Prevent actually submitting the form - e.preventDefault(); }, /* ------^------- @@ -1214,11 +1191,10 @@ var UI = { var screen = UI.screenSize(); - if (screen && UI.connected && UI.rfb.get_display()) { + if (screen && UI.connected) { - var display = UI.rfb.get_display(); var resizeMode = UI.getSetting('resize'); - display.set_scale(1); + UI.rfb.viewportScale = 1.0; // Make sure the viewport is adjusted first UI.updateViewClip(); @@ -1250,19 +1226,17 @@ var UI = { if (!UI.rfb) return; var resizeMode = UI.getSetting('resize'); - if (resizeMode !== 'scale' && resizeMode !== 'downscale') { + if (resizeMode !== 'scale') { return; } var screen = UI.screenSize(); - if (!screen || !UI.connected || !UI.rfb.get_display()) { + if (!screen || !UI.connected) { return; } - var display = UI.rfb.get_display(); - var downscaleOnly = resizeMode === 'downscale'; - display.autoscale(screen.w, screen.h, downscaleOnly); + UI.rfb.autoscale(screen.w, screen.h); UI.fixScrollbars(); }, @@ -1275,13 +1249,14 @@ var UI = { // Normally we only apply the current resize mode after a window resize // event. This means that when a new connection is opened, there is no // resize mode active. - // We have to wait until the first FBU because this is where the client - // will find the supported encodings of the server. Some calls later in - // the chain is dependant on knowing the server-capabilities. - initialResize: function(rfb, fbu) { + // We have to wait until we know the capabilities of the server as + // some calls later in the chain is dependant on knowing the + // server-capabilities. + initialResize: function() { + if (UI.doneInitialResize) return; + UI.applyResizeMode(); - // After doing this once, we remove the callback. - UI.rfb.set_onFBUComplete(function() { }); + UI.doneInitialResize = true; }, /* ------^------- @@ -1300,12 +1275,11 @@ var UI = { updateViewClip: function() { if (!UI.rfb) return; - var display = UI.rfb.get_display(); - var cur_clip = display.get_viewport(); + var cur_clip = UI.rfb.clipViewport; var new_clip = UI.getSetting('view_clip'); var resizeSetting = UI.getSetting('resize'); - if (resizeSetting === 'downscale' || resizeSetting === 'scale') { + if (resizeSetting === 'scale') { // Disable viewport clipping if we are scaling new_clip = false; } else if (isTouchDevice) { @@ -1314,7 +1288,7 @@ var UI = { } if (cur_clip !== new_clip) { - display.set_viewport(new_clip); + UI.rfb.clipViewport = new_clip; } var size = UI.screenSize(); @@ -1322,7 +1296,7 @@ var UI = { if (new_clip && size) { // When clipping is enabled, the screen is limited to // the size of the browser window. - display.viewportChangeSize(size.w, size.h); + UI.rfb.viewportChangeSize(size.w, size.h); UI.fixScrollbars(); } @@ -1335,7 +1309,7 @@ var UI = { enableDisableViewClip: function() { var resizeSetting = UI.getSetting('resize'); // Disable clipping if we are scaling, connected or on touch - if (resizeSetting === 'downscale' || resizeSetting === 'scale' || + if (resizeSetting === 'scale' || isTouchDevice) { UI.disableSetting('view_clip'); } else { @@ -1352,7 +1326,7 @@ var UI = { toggleViewDrag: function() { if (!UI.rfb) return; - var drag = UI.rfb.get_viewportDrag(); + var drag = UI.rfb.dragViewport; UI.setViewDrag(!drag); }, @@ -1360,7 +1334,7 @@ var UI = { setViewDrag: function(drag) { if (!UI.rfb) return; - UI.rfb.set_viewportDrag(drag); + UI.rfb.dragViewport = drag; UI.updateViewDrag(); }, @@ -1372,22 +1346,21 @@ var UI = { // Check if viewport drag is possible. It is only possible // if the remote display is clipping the client display. - if (UI.rfb.get_display().get_viewport() && - UI.rfb.get_display().clippingDisplay()) { + if (UI.rfb.clipViewport && UI.rfb.isClipped) { clipping = true; } var viewDragButton = document.getElementById('noVNC_view_drag_button'); if (!clipping && - UI.rfb.get_viewportDrag()) { + UI.rfb.dragViewport) { // The size of the remote display is the same or smaller // than the client display. Make sure viewport drag isn't // active when it can't be used. - UI.rfb.set_viewportDrag(false); + UI.rfb.dragViewport = false; } - if (UI.rfb.get_viewportDrag()) { + if (UI.rfb.dragViewport) { viewDragButton.classList.add("noVNC_selected"); } else { viewDragButton.classList.remove("noVNC_selected"); @@ -1655,9 +1628,9 @@ var UI = { * ------v------*/ setMouseButton: function(num) { - var view_only = UI.rfb.get_view_only(); + var view_only = UI.rfb.viewOnly; if (UI.rfb && !view_only) { - UI.rfb.get_mouse().set_touchButton(num); + UI.rfb.touchButton = num; } var blist = [0, 1,2,4]; @@ -1672,21 +1645,16 @@ var UI = { } }, - updateLocalCursor: function() { - if (!UI.rfb) return; - UI.rfb.set_local_cursor(UI.getSetting('cursor')); - }, - updateViewOnly: function() { if (!UI.rfb) return; - UI.rfb.set_view_only(UI.getSetting('view_only')); + UI.rfb.viewOnly = UI.getSetting('view_only'); }, updateLogging: function() { WebUtil.init_logging(UI.getSetting('logging')); }, - updateSessionSize: function(rfb, width, height) { + updateSessionSize: function(e) { UI.updateViewClip(); UI.updateScaling(); UI.fixScrollbars(); @@ -1704,13 +1672,13 @@ var UI = { screen.style.overflow = ""; }, - updateDesktopName: function(rfb, name) { - UI.desktopName = name; + updateDesktopName: function(e) { + UI.desktopName = e.detail.name; // Display the desktop name in the document title - document.title = name + " - noVNC"; + document.title = e.detail.name + " - noVNC"; }, - bell: function(rfb) { + bell: function(e) { if (WebUtil.getConfigVar('bell', 'on') === 'on') { var promise = document.getElementById('noVNC_bell').play(); // The standards disagree on the return value here diff --git a/core/display.js b/core/display.js index e3f4293a..e61802a6 100644 --- a/core/display.js +++ b/core/display.js @@ -10,12 +10,10 @@ /*jslint browser: true, white: false */ /*global Util, Base64, changeCursor */ -import { browserSupportsCursorURIs as cursorURIsSupported } from './util/browsers.js'; -import { set_defaults, make_properties } from './util/properties.js'; import * as Log from './util/logging.js'; import Base64 from "./base64.js"; -export default function Display(defaults) { +export default function Display(target) { this._drawCtx = null; this._c_forceCanvas = false; @@ -32,16 +30,11 @@ export default function Display(defaults) { this._tile_x = 0; this._tile_y = 0; - set_defaults(this, defaults, { - 'scale': 1.0, - 'viewport': false, - 'render_mode': '', - "onFlush": function () {}, - }); - Log.Debug(">> Display.constructor"); // The visible canvas + this._target = target; + if (!this._target) { throw new Error("Target must be set"); } @@ -72,23 +65,10 @@ export default function Display(defaults) { this.clear(); // Check canvas features - if ('createImageData' in this._drawCtx) { - this._render_mode = 'canvas rendering'; - } else { + if (!('createImageData' in this._drawCtx)) { throw new Error("Canvas does not support createImageData"); } - if (this._prefer_js === null) { - Log.Info("Prefering javascript operations"); - this._prefer_js = true; - } - - // Determine browser support for setting the cursor via data URI scheme - if (this._cursor_uri || this._cursor_uri === null || - this._cursor_uri === undefined) { - this._cursor_uri = cursorURIsSupported(); - } - Log.Debug("<< Display.constructor"); }; @@ -101,13 +81,50 @@ try { } Display.prototype = { - // Public methods + // ===== PROPERTIES ===== + + _scale: 1.0, + get scale() { return this._scale; }, + set scale(scale) { + this._rescale(scale); + }, + + _clipViewport: false, + get clipViewport() { return this._clipViewport; }, + set clipViewport(viewport) { + this._clipViewport = viewport; + // May need to readjust the viewport dimensions + var vp = this._viewportLoc; + this.viewportChangeSize(vp.w, vp.h); + this.viewportChangePos(0, 0); + }, + + get width() { + return this._fb_width; + }, + get height() { + return this._fb_height; + }, + + get isClipped() { + var vp = this._viewportLoc; + return this._fb_width > vp.w || this._fb_height > vp.h; + }, + + logo: null, + + // ===== EVENT HANDLERS ===== + + onflush: function () {}, // A flush request has finished + + // ===== PUBLIC METHODS ===== + viewportChangePos: function (deltaX, deltaY) { var vp = this._viewportLoc; deltaX = Math.floor(deltaX); deltaY = Math.floor(deltaY); - if (!this._viewport) { + if (!this._clipViewport) { deltaX = -vp.w; // clamped later of out of bounds deltaY = -vp.h; } @@ -146,7 +163,7 @@ Display.prototype = { viewportChangeSize: function(width, height) { - if (!this._viewport || + if (!this._clipViewport || typeof(width) === "undefined" || typeof(height) === "undefined") { @@ -307,7 +324,7 @@ Display.prototype = { flush: function() { if (this._renderQ.length === 0) { - this._onFlush(); + this.onflush(); } else { this._flushing = true; } @@ -382,56 +399,45 @@ Display.prototype = { this._tile = this._drawCtx.createImageData(width, height); } - if (this._prefer_js) { - var red = color[2]; - var green = color[1]; - var blue = color[0]; + var red = color[2]; + var green = color[1]; + var blue = color[0]; - var data = this._tile.data; - for (var i = 0; i < width * height * 4; i += 4) { - data[i] = red; - data[i + 1] = green; - data[i + 2] = blue; - data[i + 3] = 255; - } - } else { - this.fillRect(x, y, width, height, color, true); + var data = this._tile.data; + for (var i = 0; i < width * height * 4; i += 4) { + data[i] = red; + data[i + 1] = green; + data[i + 2] = blue; + data[i + 3] = 255; } }, // update sub-rectangle of the current tile subTile: function (x, y, w, h, color) { - if (this._prefer_js) { - var red = color[2]; - var green = color[1]; - var blue = color[0]; - var xend = x + w; - var yend = y + h; + var red = color[2]; + var green = color[1]; + var blue = color[0]; + var xend = x + w; + var yend = y + h; - var data = this._tile.data; - var width = this._tile.width; - for (var j = y; j < yend; j++) { - for (var i = x; i < xend; i++) { - var p = (i + (j * width)) * 4; - data[p] = red; - data[p + 1] = green; - data[p + 2] = blue; - data[p + 3] = 255; - } + var data = this._tile.data; + var width = this._tile.width; + for (var j = y; j < yend; j++) { + for (var i = x; i < xend; i++) { + var p = (i + (j * width)) * 4; + data[p] = red; + data[p + 1] = green; + data[p + 2] = blue; + data[p + 3] = 255; } - } else { - this.fillRect(this._tile_x + x, this._tile_y + y, w, h, color, true); } }, // draw the current tile to the screen finishTile: function () { - if (this._prefer_js) { - this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); - this._damage(this._tile_x, this._tile_y, - this._tile.width, this._tile.height); - } - // else: No-op -- already done by setSubTile + this._drawCtx.putImageData(this._tile, this._tile_x, this._tile_y); + this._damage(this._tile_x, this._tile_y, + this._tile.width, this._tile.height); }, blitImage: function (x, y, width, height, arr, offset, from_queue) { @@ -500,11 +506,6 @@ Display.prototype = { }, changeCursor: function (pixels, mask, hotx, hoty, w, h) { - if (this._cursor_uri === false) { - Log.Warn("changeCursor called but no cursor data URI support"); - return; - } - Display.changeCursor(this._target, pixels, mask, hotx, hoty, w, h); }, @@ -516,32 +517,7 @@ Display.prototype = { this._target.style.cursor = "none"; }, - clippingDisplay: function () { - var vp = this._viewportLoc; - return this._fb_width > vp.w || this._fb_height > vp.h; - }, - - // Overridden getters/setters - set_scale: function (scale) { - this._rescale(scale); - }, - - set_viewport: function (viewport) { - this._viewport = viewport; - // May need to readjust the viewport dimensions - var vp = this._viewportLoc; - this.viewportChangeSize(vp.w, vp.h); - this.viewportChangePos(0, 0); - }, - - get_width: function () { - return this._fb_width; - }, - get_height: function () { - return this._fb_height; - }, - - autoscale: function (containerWidth, containerHeight, downscaleOnly) { + autoscale: function (containerWidth, containerHeight) { var vp = this._viewportLoc; var targetAspectRatio = containerWidth / containerHeight; var fbAspectRatio = vp.w / vp.h; @@ -553,14 +529,11 @@ Display.prototype = { scaleRatio = containerHeight / vp.h; } - if (scaleRatio > 1.0 && downscaleOnly) { - scaleRatio = 1.0; - } - this._rescale(scaleRatio); }, - // Private Methods + // ===== PRIVATE METHODS ===== + _rescale: function (factor) { this._scale = factor; var vp = this._viewportLoc; @@ -685,28 +658,11 @@ Display.prototype = { if (this._renderQ.length === 0 && this._flushing) { this._flushing = false; - this._onFlush(); + this.onflush(); } }, }; -make_properties(Display, [ - ['target', 'wo', 'dom'], // Canvas element for rendering - ['context', 'ro', 'raw'], // Canvas 2D context for rendering (read-only) - ['logo', 'rw', 'raw'], // Logo to display when cleared: {"width": w, "height": h, "type": mime-type, "data": data} - ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 - ['viewport', 'rw', 'bool'], // Use viewport clipping - ['width', 'ro', 'int'], // Display area width - ['height', 'ro', 'int'], // Display area height - - ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) - - ['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods - ['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI - - ['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished -]); - // Class Methods Display.changeCursor = function (target, pixels, mask, hotx, hoty, w, h) { if ((w === 0) || (h === 0)) { diff --git a/core/input/keyboard.js b/core/input/keyboard.js index fa4a5ae4..464d1ce8 100644 --- a/core/input/keyboard.js +++ b/core/input/keyboard.js @@ -10,7 +10,6 @@ import * as Log from '../util/logging.js'; import { stopEvent } from '../util/events.js'; -import { set_defaults, make_properties } from '../util/properties.js'; import * as KeyboardUtil from "./util.js"; import KeyTable from "./keysym.js"; @@ -18,15 +17,13 @@ import KeyTable from "./keysym.js"; // Keyboard event handler // -export default function Keyboard(defaults) { +export default function Keyboard(target) { + this._target = target || null; + this._keyDownList = {}; // List of depressed keys // (even if they are happy) this._pendingKey = null; // Key waiting for keypress - set_defaults(this, defaults, { - 'target': null, - }); - // keep these here so we can refer to them later this._eventHandlers = { 'keyup': this._handleKeyUp.bind(this), @@ -56,14 +53,14 @@ function isEdge() { } Keyboard.prototype = { - // private methods + // ===== EVENT HANDLERS ===== + + onkeyevent: function () {}, // Handler for key press/release + + // ===== PRIVATE METHODS ===== _sendKeyEvent: function (keysym, code, down) { - if (!this._onKeyEvent) { - return; - } - - Log.Debug("onKeyEvent " + (down ? "down" : "up") + + Log.Debug("onkeyevent " + (down ? "down" : "up") + ", keysym: " + keysym, ", code: " + code); // Windows sends CtrlLeft+AltRight when you press @@ -77,19 +74,19 @@ Keyboard.prototype = { ('ControlLeft' in this._keyDownList) && ('AltRight' in this._keyDownList)) { fakeAltGraph = true; - this._onKeyEvent(this._keyDownList['AltRight'], + this.onkeyevent(this._keyDownList['AltRight'], 'AltRight', false); - this._onKeyEvent(this._keyDownList['ControlLeft'], + this.onkeyevent(this._keyDownList['ControlLeft'], 'ControlLeft', false); } } - this._onKeyEvent(keysym, code, down); + this.onkeyevent(keysym, code, down); if (fakeAltGraph) { - this._onKeyEvent(this._keyDownList['ControlLeft'], + this.onkeyevent(this._keyDownList['ControlLeft'], 'ControlLeft', true); - this._onKeyEvent(this._keyDownList['AltRight'], + this.onkeyevent(this._keyDownList['AltRight'], 'AltRight', true); } }, @@ -305,7 +302,7 @@ Keyboard.prototype = { Log.Debug("<< Keyboard.allKeysUp"); }, - // Public methods + // ===== PUBLIC METHODS ===== grab: function () { //Log.Debug(">> Keyboard.grab"); @@ -336,9 +333,3 @@ Keyboard.prototype = { //Log.Debug(">> Keyboard.ungrab"); }, }; - -make_properties(Keyboard, [ - ['target', 'wo', 'dom'], // DOM element that captures keyboard input - - ['onKeyEvent', 'rw', 'func'] // Handler for key press/release -]); diff --git a/core/input/mouse.js b/core/input/mouse.js index 49b5c395..eaf908ad 100644 --- a/core/input/mouse.js +++ b/core/input/mouse.js @@ -11,13 +11,13 @@ import * as Log from '../util/logging.js'; import { isTouchDevice } from '../util/browsers.js'; import { setCapture, stopEvent, getPointerEvent } from '../util/events.js'; -import { set_defaults, make_properties } from '../util/properties.js'; var WHEEL_STEP = 10; // Delta threshold for a mouse wheel step var WHEEL_STEP_TIMEOUT = 50; // ms var WHEEL_LINE_HEIGHT = 19; -export default function Mouse(defaults) { +export default function Mouse(target) { + this._target = target || document; this._doubleClickTimer = null; this._lastTouchPos = null; @@ -28,12 +28,6 @@ export default function Mouse(defaults) { this._accumulatedWheelDeltaX = 0; this._accumulatedWheelDeltaY = 0; - // Configuration attributes - set_defaults(this, defaults, { - 'target': document, - 'touchButton': 1 - }); - this._eventHandlers = { 'mousedown': this._handleMouseDown.bind(this), 'mouseup': this._handleMouseUp.bind(this), @@ -44,7 +38,16 @@ export default function Mouse(defaults) { }; Mouse.prototype = { - // private methods + // ===== PROPERTIES ===== + + touchButton: 1, // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) + + // ===== EVENT HANDLERS ===== + + onmousebutton: function () {}, // Handler for mouse button click/release + onmousemove: function () {}, // Handler for mouse movement + + // ===== PRIVATE METHODS ===== _resetDoubleClickTimer: function () { this._doubleClickTimer = null; @@ -83,7 +86,7 @@ Mouse.prototype = { } this._doubleClickTimer = setTimeout(this._resetDoubleClickTimer.bind(this), 500); } - bmask = this._touchButton; + bmask = this.touchButton; // If bmask is set } else if (e.which) { /* everything except IE */ @@ -95,11 +98,10 @@ Mouse.prototype = { (e.button & 0x4) / 2; // Middle } - if (this._onMouseButton) { - Log.Debug("onMouseButton " + (down ? "down" : "up") + - ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); - this._onMouseButton(pos.x, pos.y, down, bmask); - } + Log.Debug("onmousebutton " + (down ? "down" : "up") + + ", x: " + pos.x + ", y: " + pos.y + ", bmask: " + bmask); + this.onmousebutton(pos.x, pos.y, down, bmask); + stopEvent(e); }, @@ -122,11 +124,11 @@ Mouse.prototype = { _generateWheelStepX: function () { if (this._accumulatedWheelDeltaX < 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 5); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 5); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 5); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 5); } else if (this._accumulatedWheelDeltaX > 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 6); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 6); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 6); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 6); } this._accumulatedWheelDeltaX = 0; @@ -135,11 +137,11 @@ Mouse.prototype = { _generateWheelStepY: function () { if (this._accumulatedWheelDeltaY < 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 3); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 3); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 3); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 3); } else if (this._accumulatedWheelDeltaY > 0) { - this._onMouseButton(this._pos.x, this._pos.y, 1, 1 << 4); - this._onMouseButton(this._pos.x, this._pos.y, 0, 1 << 4); + this.onmousebutton(this._pos.x, this._pos.y, 1, 1 << 4); + this.onmousebutton(this._pos.x, this._pos.y, 0, 1 << 4); } this._accumulatedWheelDeltaY = 0; @@ -153,8 +155,6 @@ Mouse.prototype = { }, _handleMouseWheel: function (e) { - if (!this._onMouseButton) { return; } - this._resetWheelStepTimers(); this._updateMousePosition(e); @@ -199,9 +199,7 @@ Mouse.prototype = { _handleMouseMove: function (e) { this._updateMousePosition(e); - if (this._onMouseMove) { - this._onMouseMove(this._pos.x, this._pos.y); - } + this.onmousemove(this._pos.x, this._pos.y); stopEvent(e); }, @@ -240,7 +238,8 @@ Mouse.prototype = { this._pos = {x:x, y:y}; }, - // Public methods + // ===== PUBLIC METHODS ===== + grab: function () { var c = this._target; @@ -282,11 +281,3 @@ Mouse.prototype = { c.removeEventListener('contextmenu', this._eventHandlers.mousedisable); } }; - -make_properties(Mouse, [ - ['target', 'ro', 'dom'], // DOM element that captures mouse input - - ['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release - ['onMouseMove', 'rw', 'func'], // Handler for mouse movement - ['touchButton', 'rw', 'int'] // Button mask (1, 2, 4) for touch devices (0 means ignore clicks) -]); diff --git a/core/rfb.js b/core/rfb.js index a16093b6..3d12ae4b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -13,7 +13,8 @@ import * as Log from './util/logging.js'; import _ from './util/localization.js'; import { decodeUTF8 } from './util/strings.js'; -import { set_defaults, make_properties } from './util/properties.js'; +import { browserSupportsCursorURIs, isTouchDevice } from './util/browsers.js'; +import EventTargetMixin from './util/eventtarget.js'; import Display from "./display.js"; import Keyboard from "./input/keyboard.js"; import Mouse from "./input/mouse.js"; @@ -24,50 +25,78 @@ import KeyTable from "./input/keysym.js"; import XtScancode from "./input/xtscancodes.js"; import Inflator from "./inflator.js"; import { encodings, encodingName } from "./encodings.js"; +import "./util/polyfill.js"; /*jslint white: false, browser: true */ /*global window, Util, Display, Keyboard, Mouse, Websock, Websock_native, Base64, DES, KeyTable, Inflator, XtScancode */ -export default function RFB(defaults) { - "use strict"; - if (!defaults) { - defaults = {}; +// How many seconds to wait for a disconnect to finish +var DISCONNECT_TIMEOUT = 3; + +export default function RFB(target, url, options) { + if (!target) { + throw Error("Must specify target"); + } + if (!url) { + throw Error("Must specify URL"); } - this._rfb_host = ''; - this._rfb_port = 5900; - this._rfb_password = ''; - this._rfb_path = ''; + this._target = target; + this._url = url; + // Connection details + options = options || {} + this._rfb_credentials = options.credentials || {}; + this._shared = 'shared' in options ? !!options.shared : true; + this._repeaterID = options.repeaterID || ''; + + // Internal state this._rfb_connection_state = ''; this._rfb_init_state = ''; - this._rfb_version = 0; - this._rfb_max_version = 3.8; this._rfb_auth_scheme = ''; this._rfb_disconnect_reason = ""; + // Server capabilities + this._rfb_version = 0; + this._rfb_max_version = 3.8; this._rfb_tightvnc = false; this._rfb_xvp_ver = 0; - this._encHandlers = {}; - this._encStats = {}; + this._fb_width = 0; + this._fb_height = 0; - this._sock = null; // Websock object - this._display = null; // Display object - this._flushing = false; // Display flushing state - this._keyboard = null; // Keyboard input handler object - this._mouse = null; // Mouse input handler object - this._disconnTimer = null; // disconnection timer + this._fb_name = ""; + + this._capabilities = { power: false, resize: false }; this._supportsFence = false; this._supportsContinuousUpdates = false; this._enabledContinuousUpdates = false; - // Frame buffer update state + this._supportsSetDesktopSize = false; + this._screen_id = 0; + this._screen_flags = 0; + + this._qemuExtKeyEventSupported = false; + + // Internal objects + this._sock = null; // Websock object + this._display = null; // Display object + this._flushing = false; // Display flushing state + this._keyboard = null; // Keyboard input handler object + this._mouse = null; // Mouse input handler object + + // Timers + this._disconnTimer = null; // disconnection timer + + // Decoder states and stats + this._encHandlers = {}; + this._encStats = {}; + this._FBU = { rects: 0, - subrects: 0, // RRE + subrects: 0, // RRE and HEXTILE lines: 0, // RAW tiles: 0, // HEXTILE bytes: 0, @@ -78,12 +107,11 @@ export default function RFB(defaults) { encoding: 0, subencoding: -1, background: null, - zlib: [] // TIGHT zlib streams + zlibs: [] // TIGHT zlib streams }; - - this._fb_width = 0; - this._fb_height = 0; - this._fb_name = ""; + for (var i = 0; i < 4; i++) { + this._FBU.zlibs[i] = new Inflator(); + } this._destBuff = null; this._paletteBuff = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel) @@ -103,10 +131,6 @@ export default function RFB(defaults) { pixels: 0 }; - this._supportsSetDesktopSize = false; - this._screen_id = 0; - this._screen_flags = 0; - // Mouse state this._mouse_buttonMask = 0; this._mouse_arr = []; @@ -114,37 +138,6 @@ export default function RFB(defaults) { this._viewportDragPos = {}; this._viewportHasMoved = false; - // QEMU Extended Key Event support - default to false - this._qemuExtKeyEventSupported = false; - - // set the default value on user-facing properties - set_defaults(this, defaults, { - 'target': 'null', // VNC display rendering Canvas object - 'encrypt': false, // Use TLS/SSL/wss encryption - 'local_cursor': false, // Request locally rendered cursor - 'shared': true, // Request shared mode - 'view_only': false, // Disable client mouse/keyboard - 'focus_on_click': true, // Grab focus on canvas on mouse click - 'xvp_password_sep': '@', // Separator for XVP password fields - 'disconnectTimeout': 3, // Time (s) to wait for disconnection - 'wsProtocols': ['binary'], // Protocols to use in the WebSocket connection - 'repeaterID': '', // [UltraVNC] RepeaterID to connect to - 'viewportDrag': false, // Move the viewport on mouse drags - - // Callback functions - 'onUpdateState': function () { }, // onUpdateState(rfb, state, oldstate): connection state change - 'onNotification': function () { }, // onNotification(rfb, msg, level, options): notification for UI - 'onDisconnected': function () { }, // onDisconnected(rfb, reason): disconnection finished - 'onPasswordRequired': function () { }, // onPasswordRequired(rfb, msg): VNC password is required - 'onClipboard': function () { }, // onClipboard(rfb, text): RFB clipboard contents received - 'onBell': function () { }, // onBell(rfb): RFB Bell message received - 'onFBUReceive': function () { }, // onFBUReceive(rfb, rect): RFB FBU rect received but not yet processed - 'onFBUComplete': function () { }, // onFBUComplete(rfb): RFB FBU received and processed - 'onFBResize': function () { }, // onFBResize(rfb, width, height): frame buffer resized - 'onDesktopName': function () { }, // onDesktopName(rfb, name): desktop name received - 'onXvpInit': function () { } // onXvpInit(version): XVP extensions active for this connection - }); - // Bound event handlers this._eventHandlers = { focusCanvas: this._focusCanvas.bind(this), @@ -174,19 +167,20 @@ export default function RFB(defaults) { // NB: nothing that needs explicit teardown should be done // before this point, since this can throw an exception try { - this._display = new Display({target: this._target, - onFlush: this._onFlush.bind(this)}); + this._display = new Display(this._target); } catch (exc) { Log.Error("Display exception: " + exc); throw exc; } + this._display.onflush = this._onFlush.bind(this); + this._display.clear(); - this._keyboard = new Keyboard({target: this._target, - onKeyEvent: this._handleKeyEvent.bind(this)}); + this._keyboard = new Keyboard(this._target); + this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); - this._mouse = new Mouse({target: this._target, - onMouseButton: this._handleMouseButton.bind(this), - onMouseMove: this._handleMouseMove.bind(this)}); + this._mouse = new Mouse(this._target); + this._mouse.onmousebutton = this._handleMouseButton.bind(this); + this._mouse.onmousemove = this._handleMouseMove.bind(this); this._sock = new Websock(); this._sock.on('message', this._handle_message.bind(this)); @@ -236,33 +230,51 @@ export default function RFB(defaults) { Log.Warn("WebSocket on-error event"); }); - this._init_vars(); - this._cleanup(); - - var rmode = this._display.get_render_mode(); - Log.Info("Using native WebSockets, render mode: " + rmode); + // Slight delay of the actual connection so that the caller has + // time to set up callbacks + setTimeout(this._updateConnectionState.bind(this, 'connecting')); Log.Debug("<< RFB.constructor"); }; RFB.prototype = { - // Public methods - connect: function (host, port, password, path) { - this._rfb_host = host; - this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; - this._rfb_path = (path !== undefined) ? path : ""; + // ===== PROPERTIES ===== - if (!this._rfb_host) { - return this._fail( - _("Must set host")); + dragViewport: false, + focusOnClick: true, + + _viewOnly: false, + get viewOnly() { return this._viewOnly; }, + set viewOnly(viewOnly) { + this._viewOnly = viewOnly; + + if (this._rfb_connection_state === "connecting" || + this._rfb_connection_state === "connected") { + if (viewOnly) { + this._keyboard.ungrab(); + this._mouse.ungrab(); + } else { + this._keyboard.grab(); + this._mouse.grab(); + } } - - this._rfb_init_state = ''; - this._updateConnectionState('connecting'); - return true; }, + get capabilities() { return this._capabilities; }, + + get touchButton() { return this._mouse.touchButton; }, + set touchButton(button) { this._mouse.touchButton = button; }, + + get viewportScale() { return this._display.scale; }, + set viewportScale(scale) { this._display.scale = scale; }, + + get clipViewport() { return this._display.clipViewport; }, + set clipViewport(viewport) { this._display.clipViewport = viewport; }, + + get isClipped() { return this._display.isClipped; }, + + // ===== PUBLIC METHODS ===== + disconnect: function () { this._updateConnectionState('disconnecting'); this._sock.off('error'); @@ -270,13 +282,13 @@ RFB.prototype = { this._sock.off('open'); }, - sendPassword: function (passwd) { - this._rfb_password = passwd; + sendCredentials: function (creds) { + this._rfb_credentials = creds; setTimeout(this._init_msg.bind(this), 0); }, sendCtrlAltDel: function () { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } + if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } Log.Info("Sending Ctrl-Alt-Del"); this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true); @@ -285,38 +297,29 @@ RFB.prototype = { this.sendKey(KeyTable.XK_Delete, "Delete", false); this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false); this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false); - - return true; }, - xvpOp: function (ver, op) { - if (this._rfb_xvp_ver < ver) { return false; } - Log.Info("Sending XVP operation " + op + " (version " + ver + ")"); - this._sock.send_string("\xFA\x00" + String.fromCharCode(ver) + String.fromCharCode(op)); - return true; + machineShutdown: function () { + this._xvpOp(1, 2); }, - xvpShutdown: function () { - return this.xvpOp(1, 2); + machineReboot: function () { + this._xvpOp(1, 3); }, - xvpReboot: function () { - return this.xvpOp(1, 3); - }, - - xvpReset: function () { - return this.xvpOp(1, 4); + machineReset: function () { + this._xvpOp(1, 4); }, // Send a key press. If 'down' is not specified then send a down key // followed by an up key. sendKey: function (keysym, code, down) { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; } + if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } if (down === undefined) { this.sendKey(keysym, code, true); this.sendKey(keysym, code, false); - return true; + return; } var scancode = XtScancode[code]; @@ -330,63 +333,55 @@ RFB.prototype = { RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode); } else { if (!keysym) { - return false; + return; } Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym); RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0); } - - return true; }, clipboardPasteFrom: function (text) { - if (this._rfb_connection_state !== 'connected' || this._view_only) { return; } + if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } RFB.messages.clientCutText(this._sock, text); }, + autoscale: function (width, height) { + if (this._rfb_connection_state !== 'connected') { return; } + this._display.autoscale(width, height); + }, + + viewportChangeSize: function(width, height) { + if (this._rfb_connection_state !== 'connected') { return; } + this._display.viewportChangeSize(width, height); + }, + // Requests a change of remote desktop size. This message is an extension // and may only be sent if we have received an ExtendedDesktopSize message requestDesktopSize: function (width, height) { if (this._rfb_connection_state !== 'connected' || - this._view_only) { - return false; + this._viewOnly) { + return; } - if (this._supportsSetDesktopSize) { - RFB.messages.setDesktopSize(this._sock, width, height, - this._screen_id, this._screen_flags); - this._sock.flush(); - return true; - } else { - return false; + if (!this._supportsSetDesktopSize) { + return; } + + RFB.messages.setDesktopSize(this._sock, width, height, + this._screen_id, this._screen_flags); }, - // Private methods + // ===== PRIVATE METHODS ===== _connect: function () { Log.Debug(">> RFB.connect"); - this._init_vars(); - var uri; - if (typeof UsingSocketIO !== 'undefined') { - uri = 'http'; - } else { - uri = this._encrypt ? 'wss' : 'ws'; - } - - uri += '://' + this._rfb_host; - if(this._rfb_port) { - uri += ':' + this._rfb_port; - } - uri += '/' + this._rfb_path; - - Log.Info("connecting to " + uri); + Log.Info("connecting to " + this._url); try { // WebSocket.onopen transitions to the RFB init states - this._sock.open(uri, this._wsProtocols); + this._sock.open(this._url, ['binary']); } catch (e) { if (e.name === 'SyntaxError') { this._fail("Invalid host or port value given", e); @@ -412,29 +407,6 @@ RFB.prototype = { Log.Debug("<< RFB.disconnect"); }, - _init_vars: function () { - // reset state - this._FBU.rects = 0; - this._FBU.subrects = 0; // RRE and HEXTILE - this._FBU.lines = 0; // RAW - this._FBU.tiles = 0; // HEXTILE - this._FBU.zlibs = []; // TIGHT zlib encoders - this._mouse_buttonMask = 0; - this._mouse_arr = []; - this._rfb_tightvnc = false; - - // Clear the per connection encoding stats - var stats = this._encStats; - Object.keys(stats).forEach(function (key) { - stats[key][0] = 0; - }); - - var i; - for (i = 0; i < 4; i++) { - this._FBU.zlibs[i] = new Inflator(); - } - }, - _print_stats: function () { var stats = this._encStats; @@ -454,11 +426,11 @@ RFB.prototype = { }, _cleanup: function () { - if (!this._view_only) { this._keyboard.ungrab(); } - if (!this._view_only) { this._mouse.ungrab(); } + if (!this._viewOnly) { this._keyboard.ungrab(); } + if (!this._viewOnly) { this._mouse.ungrab(); } this._display.defaultCursor(); if (Log.get_logging() !== 'debug') { - // Show noVNC logo on load and when disconnected, unless in + // Show noVNC logo when disconnected, unless in // debug mode this._display.clear(); } @@ -540,7 +512,8 @@ RFB.prototype = { // State change actions this._rfb_connection_state = state; - this._onUpdateState(this, state, oldstate); + var event = new CustomEvent("updatestate", { detail: { state: state } }); + this.dispatchEvent(event); var smsg = "New state '" + state + "', was '" + oldstate + "'."; Log.Debug(smsg); @@ -556,14 +529,16 @@ RFB.prototype = { switch (state) { case 'disconnected': - // Call onDisconnected callback after onUpdateState since + // Fire disconnected event after updatestate event since // we don't know if the UI only displays the latest message if (this._rfb_disconnect_reason !== "") { - this._onDisconnected(this, this._rfb_disconnect_reason); + event = new CustomEvent("disconnect", + { detail: { reason: this._rfb_disconnect_reason } }); } else { // No reason means clean disconnect - this._onDisconnected(this); + event = new CustomEvent("disconnect", { detail: {} }); } + this.dispatchEvent(event); break; case 'connecting': @@ -576,7 +551,7 @@ RFB.prototype = { this._disconnTimer = setTimeout(function () { this._rfb_disconnect_reason = _("Disconnect timeout"); this._updateConnectionState('disconnected'); - }.bind(this), this._disconnectTimeout * 1000); + }.bind(this), DISCONNECT_TIMEOUT * 1000); break; } }, @@ -618,11 +593,10 @@ RFB.prototype = { * Send a notification to the UI. Valid levels are: * 'normal'|'warn'|'error' * - * NOTE: Options could be added in the future. * NOTE: If this function is called multiple times, remember that the * interface could be only showing the latest notification. */ - _notification: function(msg, level, options) { + _notification: function(msg, level) { switch (level) { case 'normal': case 'warn': @@ -634,11 +608,16 @@ RFB.prototype = { return; } - if (options) { - this._onNotification(this, msg, level, options); - } else { - this._onNotification(this, msg, level); - } + var event = new CustomEvent("notification", + { detail: { message: msg, level: level } }); + this.dispatchEvent(event); + }, + + _setCapability: function (cap, val) { + this._capabilities[cap] = val; + var event = new CustomEvent("capabilities", + { detail: { capabilities: this._capabilities } }); + this.dispatchEvent(event); }, _handle_message: function () { @@ -681,7 +660,7 @@ RFB.prototype = { this._mouse_buttonMask &= ~bmask; } - if (this._viewportDrag) { + if (this.dragViewport) { if (down && !this._viewportDragging) { this._viewportDragging = true; this._viewportDragPos = {'x': x, 'y': y}; @@ -693,14 +672,14 @@ RFB.prototype = { // If the viewport didn't actually move, then treat as a mouse click event // Send the button down event here, as the button up event is sent at the end of this function - if (!this._viewportHasMoved && !this._view_only) { + if (!this._viewportHasMoved && !this._viewOnly) { RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask); } this._viewportHasMoved = false; } } - if (this._view_only) { return; } // View only, skip mouse events + if (this._viewOnly) { return; } // View only, skip mouse events if (this._rfb_connection_state !== 'connected') { return; } RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); @@ -727,7 +706,7 @@ RFB.prototype = { return; } - if (this._view_only) { return; } // View only, skip mouse events + if (this._viewOnly) { return; } // View only, skip mouse events if (this._rfb_connection_state !== 'connected') { return; } RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), this._mouse_buttonMask); @@ -768,7 +747,7 @@ RFB.prototype = { } if (is_repeater) { - var repeaterID = this._repeaterID; + var repeaterID = "ID:" + this._repeaterID; while (repeaterID.length < 250) { repeaterID += "\0"; } @@ -845,21 +824,20 @@ RFB.prototype = { // authentication _negotiate_xvp_auth: function () { - var xvp_sep = this._xvp_password_sep; - var xvp_auth = this._rfb_password.split(xvp_sep); - if (xvp_auth.length < 3) { - var msg = 'XVP credentials required (user' + xvp_sep + - 'target' + xvp_sep + 'password) -- got only ' + this._rfb_password; - this._onPasswordRequired(this, msg); + if (!this._rfb_credentials.username || + !this._rfb_credentials.password || + !this._rfb_credentials.target) { + var event = new CustomEvent("credentialsrequired", + { detail: { types: ["username", "password", "target"] } }); + this.dispatchEvent(event); return false; } - var xvp_auth_str = String.fromCharCode(xvp_auth[0].length) + - String.fromCharCode(xvp_auth[1].length) + - xvp_auth[0] + - xvp_auth[1]; + var xvp_auth_str = String.fromCharCode(this._rfb_credentials.username.length) + + String.fromCharCode(this._rfb_credentials.target.length) + + this._rfb_credentials.username + + this._rfb_credentials.target; this._sock.send_string(xvp_auth_str); - this._rfb_password = xvp_auth.slice(2).join(xvp_sep); this._rfb_auth_scheme = 2; return this._negotiate_authentication(); }, @@ -867,14 +845,16 @@ RFB.prototype = { _negotiate_std_vnc_auth: function () { if (this._sock.rQwait("auth challenge", 16)) { return false; } - if (this._rfb_password.length === 0) { - this._onPasswordRequired(this); + if (!this._rfb_credentials.password) { + var event = new CustomEvent("credentialsrequired", + { detail: { types: ["password"] } }); + this.dispatchEvent(event); return false; } // TODO(directxman12): make genDES not require an Array var challenge = Array.prototype.slice.call(this._sock.rQshiftBytes(16)); - var response = RFB.genDES(this._rfb_password, challenge); + var response = RFB.genDES(this._rfb_credentials.password, challenge); this._sock.send(response); this._rfb_init_state = "SecurityResult"; return true; @@ -1105,12 +1085,14 @@ RFB.prototype = { } // we're past the point where we could backtrack, so it's safe to call this - this._onDesktopName(this, this._fb_name); + var event = new CustomEvent("desktopname", + { detail: { name: this._fb_name } }); + this.dispatchEvent(event); this._resize(width, height); - if (!this._view_only) { this._keyboard.grab(); } - if (!this._view_only) { this._mouse.grab(); } + if (!this._viewOnly) { this._keyboard.grab(); } + if (!this._viewOnly) { this._mouse.grab(); } this._fb_depth = 24; @@ -1160,7 +1142,8 @@ RFB.prototype = { encs.push(encodings.pseudoEncodingFence); encs.push(encodings.pseudoEncodingContinuousUpdates); - if (this._local_cursor && this._fb_depth == 24) { + if (browserSupportsCursorURIs() && + !isTouchDevice && this._fb_depth == 24) { encs.push(encodings.pseudoEncodingCursor); } @@ -1219,9 +1202,11 @@ RFB.prototype = { var text = this._sock.rQshiftStr(length); - if (this._view_only) { return true; } + if (this._viewOnly) { return true; } - this._onClipboard(this, text); + var event = new CustomEvent("clipboard", + { detail: { text: text } }); + this.dispatchEvent(event); return true; }, @@ -1283,7 +1268,7 @@ RFB.prototype = { case 1: // XVP_INIT this._rfb_xvp_ver = xvp_ver; Log.Info("XVP extensions enabled (version " + this._rfb_xvp_ver + ")"); - this._onXvpInit(this._rfb_xvp_ver); + this._setCapability("power", true); break; default: this._fail("Unexpected server message", @@ -1317,7 +1302,8 @@ RFB.prototype = { case 2: // Bell Log.Debug("Bell"); - this._onBell(this); + var event = new CustomEvent("bell", { detail: {} }); + this.dispatchEvent(event); return true; case 3: // ServerCutText @@ -1398,12 +1384,6 @@ RFB.prototype = { this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) + (hdr[10] << 8) + hdr[11], 10); - this._onFBUReceive(this, - {'x': this._FBU.x, 'y': this._FBU.y, - 'width': this._FBU.width, 'height': this._FBU.height, - 'encoding': this._FBU.encoding, - 'encodingName': encodingName(this._FBU.encoding)}); - if (!this._encHandlers[this._FBU.encoding]) { this._fail("Unexpected server message", "Unsupported encoding " + @@ -1458,8 +1438,6 @@ RFB.prototype = { this._display.flip(); - this._onFBUComplete(this); - return true; // We finished this FBU }, @@ -1477,77 +1455,24 @@ RFB.prototype = { this._destBuff = new Uint8Array(this._fb_width * this._fb_height * 4); this._display.resize(this._fb_width, this._fb_height); - this._onFBResize(this, this._fb_width, this._fb_height); + + var event = new CustomEvent("fbresize", + { detail: { width: this._fb_width, + height: this._fb_height } }); + this.dispatchEvent(event); this._timing.fbu_rt_start = (new Date()).getTime(); this._updateContinuousUpdates(); - } + }, + + _xvpOp: function (ver, op) { + if (this._rfb_xvp_ver < ver) { return; } + Log.Info("Sending XVP operation " + op + " (version " + ver + ")"); + RFB.messages.xvpOp(this._sock, ver, op); + }, }; -make_properties(RFB, [ - ['target', 'wo', 'dom'], // VNC display rendering Canvas object - ['encrypt', 'rw', 'bool'], // Use TLS/SSL/wss encryption - ['local_cursor', 'rw', 'bool'], // Request locally rendered cursor - ['shared', 'rw', 'bool'], // Request shared mode - ['view_only', 'rw', 'bool'], // Disable client mouse/keyboard - ['focus_on_click', 'rw', 'bool'], // Grab focus on canvas on mouse click - ['xvp_password_sep', 'rw', 'str'], // Separator for XVP password fields - ['disconnectTimeout', 'rw', 'int'], // Time (s) to wait for disconnection - ['wsProtocols', 'rw', 'arr'], // Protocols to use in the WebSocket connection - ['repeaterID', 'rw', 'str'], // [UltraVNC] RepeaterID to connect to - ['viewportDrag', 'rw', 'bool'], // Move the viewport on mouse drags - - // Callback functions - ['onUpdateState', 'rw', 'func'], // onUpdateState(rfb, state, oldstate): connection state change - ['onNotification', 'rw', 'func'], // onNotification(rfb, msg, level, options): notification for the UI - ['onDisconnected', 'rw', 'func'], // onDisconnected(rfb, reason): disconnection finished - ['onPasswordRequired', 'rw', 'func'], // onPasswordRequired(rfb, msg): VNC password is required - ['onClipboard', 'rw', 'func'], // onClipboard(rfb, text): RFB clipboard contents received - ['onBell', 'rw', 'func'], // onBell(rfb): RFB Bell message received - ['onFBUReceive', 'rw', 'func'], // onFBUReceive(rfb, fbu): RFB FBU received but not yet processed - ['onFBUComplete', 'rw', 'func'], // onFBUComplete(rfb, fbu): RFB FBU received and processed - ['onFBResize', 'rw', 'func'], // onFBResize(rfb, width, height): frame buffer resized - ['onDesktopName', 'rw', 'func'], // onDesktopName(rfb, name): desktop name received - ['onXvpInit', 'rw', 'func'] // onXvpInit(version): XVP extensions active for this connection -]); - -RFB.prototype.set_local_cursor = function (cursor) { - if (!cursor || (cursor in {'0': 1, 'no': 1, 'false': 1})) { - this._local_cursor = false; - this._display.disableLocalCursor(); //Only show server-side cursor - } else { - if (this._display.get_cursor_uri()) { - this._local_cursor = true; - } else { - Log.Warn("Browser does not support local cursor"); - this._display.disableLocalCursor(); - } - } - - // Need to send an updated list of encodings if we are connected - if (this._rfb_connection_state === "connected") { - this._sendEncodings(); - } -}; - -RFB.prototype.set_view_only = function (view_only) { - this._view_only = view_only; - - if (this._rfb_connection_state === "connecting" || - this._rfb_connection_state === "connected") { - if (view_only) { - this._keyboard.ungrab(); - this._mouse.ungrab(); - } else { - this._keyboard.grab(); - this._mouse.grab(); - } - } -}; - -RFB.prototype.get_display = function () { return this._display; }; -RFB.prototype.get_keyboard = function () { return this._keyboard; }; -RFB.prototype.get_mouse = function () { return this._mouse; }; +Object.assign(RFB.prototype, EventTargetMixin); // Class Methods RFB.messages = { @@ -1830,7 +1755,21 @@ RFB.messages = { sock._sQlen += 10; sock.flush(); - } + }, + + xvpOp: function (sock, ver, op) { + var buff = sock._sQ; + var offset = sock._sQlen; + + buff[offset] = 250; // msg-type + buff[offset + 1] = 0; // padding + + buff[offset + 2] = ver; + buff[offset + 3] = op; + + sock._sQlen += 4; + sock.flush(); + }, }; RFB.genDES = function (password, challenge) { @@ -2361,6 +2300,8 @@ RFB.encodingHandlers = { if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } this._supportsSetDesktopSize = true; + this._setCapability("resize", true); + var number_of_screens = this._sock.rQpeek8(); this._FBU.bytes = 4 + (number_of_screens * 16); diff --git a/core/util/browsers.js b/core/util/browsers.js index 77fca12f..50f59863 100644 --- a/core/util/browsers.js +++ b/core/util/browsers.js @@ -43,11 +43,3 @@ export function browserSupportsCursorURIs () { return _cursor_uris_supported; }; - -export function _forceCursorURIs(enabled) { - if (enabled === undefined || enabled) { - _cursor_uris_supported = true; - } else { - _cursor_uris_supported = false; - } -} diff --git a/core/util/eventtarget.js b/core/util/eventtarget.js new file mode 100644 index 00000000..61bc7a1c --- /dev/null +++ b/core/util/eventtarget.js @@ -0,0 +1,40 @@ +/* + * noVNC: HTML5 VNC client + * Copyright 2017 Pierre Ossman for Cendio AB + * Licensed under MPL 2.0 (see LICENSE.txt) + * + * See README.md for usage and integration instructions. + */ + +var EventTargetMixin = { + _listeners: null, + + addEventListener: function(type, callback) { + if (!this._listeners) { + this._listeners = new Map(); + } + if (!this._listeners.has(type)) { + this._listeners.set(type, new Set()); + } + this._listeners.get(type).add(callback); + }, + + removeEventListener: function(type, callback) { + if (!this._listeners || !this._listeners.has(type)) { + return; + } + this._listeners.get(type).delete(callback); + }, + + dispatchEvent: function(event) { + if (!this._listeners || !this._listeners.has(event.type)) { + return true; + } + this._listeners.get(event.type).forEach(function (callback) { + callback.call(this, event); + }, this); + return !event.defaultPrevented; + }, +}; + +export default EventTargetMixin; diff --git a/core/util/polyfill.js b/core/util/polyfill.js new file mode 100644 index 00000000..8c600e6f --- /dev/null +++ b/core/util/polyfill.js @@ -0,0 +1,54 @@ +/* + * noVNC: HTML5 VNC client + * Copyright 2017 Pierre Ossman for noVNC + * Licensed under MPL 2.0 or any later version (see LICENSE.txt) + */ + +/* Polyfills to provide new APIs in old browsers */ + +/* Object.assign() (taken from MDN) */ +if (typeof Object.assign != 'function') { + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, "assign", { + value: function assign(target, varArgs) { // .length of function is 2 + 'use strict'; + if (target == null) { // TypeError if undefined or null + throw new TypeError('Cannot convert undefined or null to object'); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true + }); +} + +/* CustomEvent constructor (taken from MDN) */ +(function () { + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + + if (typeof window.CustomEvent !== "function") { + window.CustomEvent = CustomEvent; + } +})(); diff --git a/core/util/properties.js b/core/util/properties.js deleted file mode 100644 index e1d607e0..00000000 --- a/core/util/properties.js +++ /dev/null @@ -1,138 +0,0 @@ -/* - * noVNC: HTML5 VNC client - * Copyright (C) 2012 Joel Martin - * Licensed under MPL 2.0 (see LICENSE.txt) - * - * See README.md for usage and integration instructions. - */ - -/* - * Getter/Setter Creation Utilities - */ - -import * as Log from './logging.js'; - -function make_property (proto, name, mode, type) { - "use strict"; - - var getter; - if (type === 'arr') { - getter = function (idx) { - if (typeof idx !== 'undefined') { - return this['_' + name][idx]; - } else { - return this['_' + name]; - } - }; - } else { - getter = function () { - return this['_' + name]; - }; - } - - var make_setter = function (process_val) { - if (process_val) { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = process_val(val); - } else { - this['_' + name] = process_val(val); - } - }; - } else { - return function (val, idx) { - if (typeof idx !== 'undefined') { - this['_' + name][idx] = val; - } else { - this['_' + name] = val; - } - }; - } - }; - - var setter; - if (type === 'bool') { - setter = make_setter(function (val) { - if (!val || (val in {'0': 1, 'no': 1, 'false': 1})) { - return false; - } else { - return true; - } - }); - } else if (type === 'int') { - setter = make_setter(function (val) { return parseInt(val, 10); }); - } else if (type === 'float') { - setter = make_setter(parseFloat); - } else if (type === 'str') { - setter = make_setter(String); - } else if (type === 'func') { - setter = make_setter(function (val) { - if (!val) { - return function () {}; - } else { - return val; - } - }); - } else if (type === 'arr' || type === 'dom' || type == 'raw') { - setter = make_setter(); - } else { - throw new Error('Unknown property type ' + type); // some sanity checking - } - - // set the getter - if (typeof proto['get_' + name] === 'undefined') { - proto['get_' + name] = getter; - } - - // set the setter if needed - if (typeof proto['set_' + name] === 'undefined') { - if (mode === 'rw') { - proto['set_' + name] = setter; - } else if (mode === 'wo') { - proto['set_' + name] = function (val, idx) { - if (typeof this['_' + name] !== 'undefined') { - throw new Error(name + " can only be set once"); - } - setter.call(this, val, idx); - }; - } - } - - // make a special setter that we can use in set defaults - proto['_raw_set_' + name] = function (val, idx) { - setter.call(this, val, idx); - //delete this['_init_set_' + name]; // remove it after use - }; -}; - -export function make_properties (constructor, arr) { - "use strict"; - for (var i = 0; i < arr.length; i++) { - make_property(constructor.prototype, arr[i][0], arr[i][1], arr[i][2]); - } -}; - -export function set_defaults (obj, conf, defaults) { - var defaults_keys = Object.keys(defaults); - var conf_keys = Object.keys(conf); - var keys_obj = {}; - var i; - for (i = 0; i < defaults_keys.length; i++) { keys_obj[defaults_keys[i]] = 1; } - for (i = 0; i < conf_keys.length; i++) { keys_obj[conf_keys[i]] = 1; } - var keys = Object.keys(keys_obj); - - for (i = 0; i < keys.length; i++) { - var setter = obj['_raw_set_' + keys[i]]; - if (!setter) { - Log.Warn('Invalid property ' + keys[i]); - continue; - } - - if (keys[i] in conf) { - setter.call(obj, conf[keys[i]]); - } else { - setter.call(obj, defaults[keys[i]]); - } - } -}; - diff --git a/docs/API-internal.md b/docs/API-internal.md new file mode 100644 index 00000000..f030dc38 --- /dev/null +++ b/docs/API-internal.md @@ -0,0 +1,126 @@ +# 1. Internal Modules + +The noVNC client is composed of several internal modules that handle +rendering, input, networking, etc. Each of the modules is designed to +be cross-browser and independent from each other. + +Note however that the API of these modules is not guaranteed to be +stable, and this documentation is not maintained as well as the +official external API. + + +## 1.1 Module List + +* __Mouse__ (core/input/mouse.js): Mouse input event handler with +limited touch support. + +* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with +non-US keyboard support. Translates keyDown and keyUp events to X11 +keysym values. + +* __Display__ (core/display.js): Efficient 2D rendering abstraction +layered on the HTML5 canvas element. + +* __Websock__ (core/websock.js): Websock client from websockify +with transparent binary data support. +[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. + + +## 1.2 Callbacks + +For the Mouse, Keyboard and Display objects the callback functions are +assigned to configuration attributes, just as for the RFB object. The +WebSock module has a method named 'on' that takes two parameters: the +callback event name, and the callback function. + +## 2. Modules + +## 2.1 Mouse Module + +### 2.1.1 Configuration Attributes + +| name | type | mode | default | description +| ----------- | ---- | ---- | -------- | ------------ +| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. + +### 2.1.2 Methods + +| name | parameters | description +| ------ | ---------- | ------------ +| grab | () | Begin capturing mouse events +| ungrab | () | Stop capturing mouse events + +### 2.1.2 Callbacks + +| name | parameters | description +| ------------- | ------------------- | ------------ +| onmousebutton | (x, y, down, bmask) | Handler for mouse button click/release +| onmousemove | (x, y) | Handler for mouse movement + + +## 2.2 Keyboard Module + +### 2.2.1 Configuration Attributes + +None + +### 2.2.2 Methods + +| name | parameters | description +| ------ | ---------- | ------------ +| grab | () | Begin capturing keyboard events +| ungrab | () | Stop capturing keyboard events + +### 2.2.3 Callbacks + +| name | parameters | description +| ---------- | -------------------- | ------------ +| onkeypress | (keysym, code, down) | Handler for key press/release + + +## 2.3 Display Module + +### 2.3.1 Configuration Attributes + +| name | type | mode | default | description +| ------------ | ----- | ---- | ------- | ------------ +| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data} +| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 +| clipViewport | bool | RW | false | Use viewport clipping +| width | int | RO | | Display area width +| height | int | RO | | Display area height +| isClipped | bool | RO | | Is the remote display is larger than the client display + +### 2.3.2 Methods + +| name | parameters | description +| ------------------ | ------------------------------------------------------- | ------------ +| viewportChangePos | (deltaX, deltaY) | Move the viewport relative to the current location +| viewportChangeSize | (width, height) | Change size of the viewport +| absX | (x) | Return X relative to the remote display +| absY | (y) | Return Y relative to the remote display +| resize | (width, height) | Set width and height +| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas +| clear | () | Clear the display (show logo if set) +| pending | () | Check if there are waiting items in the render queue +| flush | () | Resume processing the render queue unless it's empty +| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle +| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area +| imageRect | (x, y, mime, arr) | Draw a rectangle with an image +| startTile | (x, y, width, height, color) | Begin updating a tile +| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile +| finishTile | () | Draw the current tile to the display +| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display +| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display +| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display +| drawImage | (img, x, y) | Draw image and track damage +| changeCursor | (pixels, mask, hotx, hoty, w, h) | Change cursor appearance +| defaultCursor | () | Restore default cursor appearance +| disableLocalCursor | () | Disable local (client-side) cursor +| autoscale | (containerWidth, containerHeight) | Scale the display + +### 2.3.3 Callbacks + +| name | parameters | description +| ------- | ---------- | ------------ +| onflush | () | A display flush has been requested and we are now ready to resume FBU processing diff --git a/docs/API.md b/docs/API.md index 2a9e93e9..8f565e93 100644 --- a/docs/API.md +++ b/docs/API.md @@ -1,255 +1,424 @@ -# 1. Modules / API +# noVNC API -The noVNC client is a composed of several modular components that handle -rendering, input, networking, etc. Each of the modules is designed to -be cross-browser and be useful as a standalone library in other -projects (see LICENSE.txt). +The interface of the noVNC client consists of a single RFB object that +is instantiated once per connection. +## RFB -## 1.1 Module List +The `RFB` object represents a single connection to a VNC server. It +communicates using a WebSocket that must provide a standard RFB +protocol stream. -* __Mouse__ (core/input/mouse.js): Mouse input event handler with -limited touch support. +### Constructor -* __Keyboard__ (core/input/keyboard.js): Keyboard input event handler with -non-US keyboard support. Translates keyDown and keyUp events to X11 -keysym values. +[`RFB()`](#rfb-1) + - Creates and returns a new `RFB` object. -* __Display__ (core/display.js): Efficient 2D rendering abstraction -layered on the HTML5 canvas element. +### Properties -* __Websock__ (core/websock.js): Websock client from websockify -with transparent binary data support. -[Websock API](https://github.com/novnc/websockify/wiki/websock.js) wiki page. +`viewOnly` + - Is a `boolean` indicating if any events (e.g. key presses or mouse + movement) should be prevented from being sent to the server. + Disabled by default. -* __RFB__ (core/rfb.js): Main class that implements the RFB -protocol and stitches the other classes together. +`focusOnClick` + - Is a `boolean` indicating if keyboard focus should automatically be + moved to the canvas when a `mousedown` or `touchstart` event is + received. +`touchButton` + - Is a `long` controlling the button mask that should be simulated + when a touch event is recieved. Uses the same values as + [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button). + Is set to `1` by default. -## 1.2 Configuration Attributes +`viewportScale` + - Is a `double` indicating how the framebuffer contents should be + scaled before being rendered on to the canvas. See also + [`RFB.autoscale()`](#rfbautoscale). Is set to `1.0` by default. -The Mouse, Keyboard, Display and RFB classes have a similar API for -configuration options. Each configuration option has a default value, -which can be overridden by a a configuration object passed to the -constructor. Configuration options can then be read and modified after -initialization with "get_*" and "set_*" methods respectively. For -example, the following initializes an RFB object with the 'encrypt' -configuration option enabled, then confirms it was set, then disables -it. +`clipViewport` + - Is a `boolean` indicating if the canvas should be clipped to its + container. When disabled the container must be able to handle the + resulting overflow. Disabled by default. - var rfb = new RFB({'encrypt': true}); - if (rfb.get_encrypt()) { - alert("Encryption is set"); - } - rfb.set_encrypt(false); +`dragViewport` + - Is a `boolean` indicating if mouse events should control the + relative position of a clipped canvas. Only relevant if + `clipViewport` is enabled. Disabled by default. -Some attributes are read-only and cannot be changed. For example, the -Display 'render_mode' option will throw an exception if an attempt is -made to set it. The attribute mode is one of the following: +`isClipped` *Read only* + - Is a `boolean` indicating if the framebuffer is larger than the + current canvas, i.e. it is being clipped. - RO - read only - RW - read write - WO - write once +`capabilities` *Read only* + - Is an `Object` indicating which optional extensions are available + on the server. Some methods may only be called if the corresponding + capability is set. The following capabilities are defined: + | name | type | description + | -------- | --------- | ----------- + | `power` | `boolean` | Machine power control is available + | `resize` | `boolean` | The framebuffer can be resized -## 1.3 Methods +### Events -In addition to the getter and setter methods to modify configuration -attributes, each of the modules has other methods that are available -in the object instance. For example, the Display module has method -named 'blitImage' which takes an array of pixel data and draws it to -the 2D canvas. +[`updatestate`](#updatestate) + - The `updatestate` event is fired when the connection state of the + `RFB` object changes. -## 1.4 Callbacks +[`notification`](#notification) + - The `notification` event is fired when the `RFB` usage has a + message to display to the user. -Each of the modules has certain events that can be hooked with -callback functions. For the Mouse, Keyboard, Display and RFB classes -the callback functions are assigned to configuration attributes. The -WebSock module has a method named 'on' that takes two parameters: the -callback event name, and the callback function. +[`disconnect`](#disconnected) + - The `disconnect` event is fired when the `RFB` object disconnects. -## 2. Modules +[`credentialsrequired`](#credentialsrequired) + - The `credentialsrequired` event is fired when more credentials must + be given to continue. -## 2.1 Mouse Module +[`clipboard`](#clipboard) + - The `clipboard` event is fired when clipboard data is received from + the server. -### 2.1.1 Configuration Attributes +[`bell`](#bell) + - The `bell` event is fired when a audible bell request is received + from the server. -| name | type | mode | default | description -| ----------- | ---- | ---- | -------- | ------------ -| target | DOM | WO | document | DOM element that captures mouse input -| touchButton | int | RW | 1 | Button mask (1, 2, 4) for which click to send on touch devices. 0 means ignore clicks. +[`fbresize`](#fbresize) + - The `fbresize` event is fired when the framebuffer size is changed. -### 2.1.2 Methods +[`desktopname`](#desktopname) + - The `desktopname` event is fired when the remote desktop name + changes. -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing mouse events -| ungrab | () | Stop capturing mouse events +[`capabilities`](#capabilities) + - The `capabilities` event is fired when `RFB.capabilities` is + updated. -### 2.1.2 Callbacks +### Methods -| name | parameters | description -| ------------- | ------------------- | ------------ -| onMouseButton | (x, y, down, bmask) | Handler for mouse button click/release -| onMouseMove | (x, y) | Handler for mouse movement +[`RFB.disconnect()`](#rfbdisconnect) + - Disconnect from the server. +[`RFB.sendCredentials()`](#rfbsendcredentials) + - Send credentials to server. Should be called after the + [`credentialsrequired`](#credentialsrequired) event has fired. -## 2.2 Keyboard Module +[`RFB.sendKey()`](#rfbsendKey) + - Send a key event. -### 2.2.1 Configuration Attributes +[`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel) + - Send Ctrl-Alt-Del key sequence. -| name | type | mode | default | description -| ------- | ---- | ---- | -------- | ------------ -| target | DOM | WO | document | DOM element that captures keyboard input +[`RFB.machineShutdown()`](#rfbmachineshutdown) + - Request a shutdown of the remote machine. -### 2.2.2 Methods +[`RFB.machineReboot()`](#rfbmachinereboot) + - Request a reboot of the remote machine. -| name | parameters | description -| ------ | ---------- | ------------ -| grab | () | Begin capturing keyboard events -| ungrab | () | Stop capturing keyboard events +[`RFB.machineReset()`](#rfbmachinereset) + - Request a reset of the remote machine. -### 2.2.3 Callbacks +[`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom) + - Send clipboard contents to server. -| name | parameters | description -| ---------- | -------------------- | ------------ -| onKeyPress | (keysym, code, down) | Handler for key press/release +[`RFB.autoscale()`](#rfbautoscale) + - Set `RFB.viewportScale` so that the framebuffer fits a specified + container. +[`RFB.requestDesktopSize()`](#rfbrequestDesktopSize) + - Send a request to change the remote desktop size. -## 2.3 Display Module +[`RFB.viewportChangeSize()`](#rfbviewportChangeSize) + - Change size of the viewport. -### 2.3.1 Configuration Attributes +### Details -| name | type | mode | default | description -| ----------- | ----- | ---- | ------- | ------------ -| target | DOM | WO | | Canvas element for rendering -| context | raw | RO | | Canvas 2D context for rendering -| logo | raw | RW | | Logo to display when cleared: {"width": width, "height": height, "type": mime-type, "data": data} -| scale | float | RW | 1.0 | Display area scale factor 0.0 - 1.0 -| viewport | bool | RW | false | Use viewport clipping -| width | int | RO | | Display area width -| height | int | RO | | Display area height -| render_mode | str | RO | '' | Canvas rendering mode -| prefer_js | str | RW | | Prefer JavaScript over canvas methods -| cursor_uri | raw | RW | | Can we render cursor using data URI +#### RFB() -### 2.3.2 Methods +The `RFB()` constructor returns a new `RFB` object and initiates a new +connection to a specified VNC server. -| name | parameters | description -| ------------------ | ------------------------------------------------------- | ------------ -| viewportChangePos | (deltaX, deltaY) | Move the viewport relative to the current location -| viewportChangeSize | (width, height) | Change size of the viewport -| absX | (x) | Return X relative to the remote display -| absY | (y) | Return Y relative to the remote display -| resize | (width, height) | Set width and height -| flip | (from_queue) | Update the visible canvas with the contents of the rendering canvas -| clear | () | Clear the display (show logo if set) -| pending | () | Check if there are waiting items in the render queue -| flush | () | Resume processing the render queue unless it's empty -| fillRect | (x, y, width, height, color, from_queue) | Draw a filled in rectangle -| copyImage | (old_x, old_y, new_x, new_y, width, height, from_queue) | Copy a rectangular area -| imageRect | (x, y, mime, arr) | Draw a rectangle with an image -| startTile | (x, y, width, height, color) | Begin updating a tile -| subTile | (tile, x, y, w, h, color) | Update a sub-rectangle within the given tile -| finishTile | () | Draw the current tile to the display -| blitImage | (x, y, width, height, arr, offset, from_queue) | Blit pixels (of R,G,B,A) to the display -| blitRgbImage | (x, y, width, height, arr, offset, from_queue) | Blit RGB encoded image to display -| blitRgbxImage | (x, y, width, height, arr, offset, from_queue) | Blit RGBX encoded image to display -| drawImage | (img, x, y) | Draw image and track damage -| changeCursor | (pixels, mask, hotx, hoty, w, h) | Change cursor appearance -| defaultCursor | () | Restore default cursor appearance -| disableLocalCursor | () | Disable local (client-side) cursor -| clippingDisplay | () | Check if the remote display is larger than the client display -| autoscale | (containerWidth, containerHeight, downscaleOnly) | Scale the display +##### Syntax -### 2.3.3 Callbacks + var rfb = new RFB( target, url [, options] ); -| name | parameters | description -| ------- | ---------- | ------------ -| onFlush | () | A display flush has been requested and we are now ready to resume FBU processing +###### Parameters +**`target`** + - A [`HTMLCanvasElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement) + that specifies where graphics should be rendered and input events + should be monitored. -## 2.4 RFB Module +**`url`** + - A `DOMString` specifying the VNC server to connect to. This must be + a valid WebSocket URL. -### 2.4.1 Configuration Attributes +**`options`** *Optional* + - An `Object` specifying extra details about how the connection + should be made. -| name | type | mode | default | description -| ----------------- | ---- | ---- | ---------- | ------------ -| target | DOM | WO | null | Canvas element for rendering (passed to Display, Mouse and Keyboard) -| encrypt | bool | RW | false | Use TLS/SSL encryption -| local_cursor | bool | RW | false | Request locally rendered cursor -| shared | bool | RW | true | Request shared VNC mode -| view_only | bool | RW | false | Disable client mouse/keyboard -| focus_on_click | bool | RW | true | Grab focus on canvas on mouse click -| xvp_password_sep | str | RW | '@' | Separator for XVP password fields -| disconnectTimeout | int | RW | 3 | Time (in seconds) to wait for disconnection -| wsProtocols | arr | RW | ['binary'] | Protocols to use in the WebSocket connection -| repeaterID | str | RW | '' | UltraVNC RepeaterID to connect to -| viewportDrag | bool | RW | false | Move the viewport on mouse drags + Possible options: -### 2.4.2 Methods + `shared` + - A `boolean` indicating if the remote server should be shared or + if any other connected clients should be disconnected. Enabled + by default. -| name | parameters | description -| ------------------ | ---------------------------- | ------------ -| connect | (host, port, password, path) | Connect to the given host:port/path. Optional password and path. -| disconnect | () | Disconnect -| sendPassword | (passwd) | Send password after onPasswordRequired callback -| sendCtrlAltDel | () | Send Ctrl-Alt-Del key sequence -| xvpOp | (ver, op) | Send a XVP operation (2=shutdown, 3=reboot, 4=reset) -| xvpShutdown | () | Send XVP shutdown. -| xvpReboot | () | Send XVP reboot. -| xvpReset | () | Send XVP reset. -| sendKey | (keysym, code, down) | Send a key press event. If down not specified, send a down and up event. -| clipboardPasteFrom | (text) | Send a clipboard paste event -| requestDesktopSize | (width, height) | Send a request to change the remote desktop size. + `credentials` + - An `Object` specifying the credentials to provide to the server + when authenticating. The following credentials are possible: -### 2.4.3 Callbacks + | name | type | description + | ------------ | ----------- | ----------- + | `"username"` | `DOMString` | The user that authenticates + | `"password"` | `DOMString` | Password for the user + | `"target"` | `DOMString` | Target machine or session -| name | parameters | description -| ------------------ | -------------------------- | ------------ -| onUpdateState | (rfb, state, oldstate) | Connection state change (see details below) -| onNotification | (rfb, msg, level, options) | Notification for the UI (optional options) -| onDisconnected | (rfb, reason) | Disconnection finished with an optional reason. No reason specified means normal disconnect. -| onPasswordRequired | (rfb, msg) | VNC password is required (use sendPassword), optionally comes with a message. -| onClipboard | (rfb, text) | RFB clipboard contents received -| onBell | (rfb) | RFB Bell message received -| onFBUReceive | (rfb, fbu) | RFB FBU received but not yet processed (see details below) -| onFBUComplete | (rfb, fbu) | RFB FBU received and processed (see details below) -| onFBResize | (rfb, width, height) | Frame buffer (remote desktop) size changed -| onDesktopName | (rfb, name) | VNC desktop name recieved -| onXvpInit | (version) | XVP extensions active for this connection. + `repeaterID` + - A `DOMString` specifying the ID to provide to any VNC repeater + encountered. +#### updatestate -__RFB onUpdateState callback details__ +The `updatestate` event is fired after the noVNC connection state +changes. The `detail` property is an `Object` containg the property +`state` with the new connection state. -The RFB module has an 'onUpdateState' callback that is invoked after -the noVNC connection state changes. Here is a list of the states that -are reported. Note that the RFB module can not transition from the -disconnected state in any way, a new instance of the object has to be -created for new connections. +Here is a list of the states that are reported: -| connection state | description -| ---------------- | ------------ -| connecting | starting to connect -| connected | connected normally -| disconnecting | starting to disconnect -| disconnected | disconnected - permanent end-state for this RFB object +| connection state | description +| ----------------- | ------------ +| `"connecting"` | starting to connect +| `"connected"` | connected normally +| `"disconnecting"` | starting to disconnect +| `"disconnected"` | disconnected -__RFB onFBUReceive and on FBUComplete callback details__ +Note that a `RFB` objects can not transition from the disconnected +state in any way, a new instance of the object has to be created for +new connections. -The onFBUReceive callback is invoked when a frame buffer update -message has been received from the server but before the RFB class has -done any additional handling. The onFBUComplete callback is invoked -with the same information but after the RFB class has handled the -message. +#### notification -The 'fbu' parameter is an object with the following structure: +The `notification` event is fired when the `RFB` object wants a message +displayed to the user. The `detail` property is an `Object` containing +the following properties: - { - x: FBU_x_position, - y: FBU_y_position, - width: FBU_width, - height: FBU_height, - encoding: FBU_encoding_number, - encodingName: FBU_encoding_string - } +| Property | Type | Description +| --------- | ----------- | ----------- +| `message` | `DOMString` | The message to display +| `level` | `DOMString` | The severity of the message + +The following levels are currently defined: + + - `"normal"` + - `"warn"` + - `"error"` + +#### disconnect + +The `disconnect` event is fired when the connection has been +terminated. The `detail` property is an `Object` the optionally +contains the property `reason`. `reason` is a `DOMString` specifying +the reason in the event of an unexpected termination. `reason` will be +omitted for a clean termination. + +#### credentialsrequired + +The `credentialsrequired` event is fired when the server requests more +credentials than were specified to [`RFB()`](#rfb-1). The `detail` +property is an `Object` containing the property `types` which is an +`Array` of `DOMString` listing the credentials that are required. + +#### clipboard + +The `clipboard` event is fired when the server has sent clipboard data. +The `detail` property is an `Object` containing the property `text` +which is a `DOMString` with the clipboard data. + +#### bell + +The `bell` event is fired when the server has requested an audible +bell. + +#### fbresize + +The `fbresize` event is fired when the framebuffer has changed +dimensions. The `detail` property is an `Object` with the properties +`width` and `height` specifying the new dimensions. + +#### desktopname + +The `desktopname` event is fired when the name of the remote desktop +changes. The `detail` property is an `Object` with the property `name` +which is a `DOMString` specifying the new name. + +#### capabilities + +The `capabilities` event is fired whenever an entry is added or removed +from `RFB.capabilities`. The `detail` property is an `Object` with the +property `capabilities` containing the new value of `RFB.capabilities`. + +#### RFB.disconnect() + +The `RFB.disconnect()` method is used to disconnect from the currently +connected server. + +##### Syntax + + RFB.disconnect( ); + +#### RFB.sendCredentials() + +The `RFB.sendCredentials()` method is used to provide the missing +credentials after a `credentialsrequired` event has been fired. + +##### Syntax + + RFB.sendCredentials( credentials ); + +###### Parameters + +**`credentials`** + - An `Object` specifying the credentials to provide to the server + when authenticating. See [`RFB()`](#rfb-1) for details. + +#### RFB.sendKey() + +The `RFB.sendKey()` method is used to send a key event to the server. + +##### Syntax + + RFB.sendKey( keysym, code [, down] ); + +###### Parameters + +**`keysym`** + - A `long` specifying the RFB keysym to send. Can be `0` if a valid + **`code`** is specified. + +**`code`** + - A `DOMString` specifying the physical key to send. Valid values are + those that can be specified to + [`KeyboardEvent.code`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code). + If the physical key cannot be determined then `null` shall be + specified. + +**`down`** *Optional* + - A `boolean` specifying if a press or a release event should be + sent. If omitted then both a press and release event are sent. + +#### RFB.sendCtrlAltDel() + +The `RFB.sendCtrlAltDel()` method is used to send the key sequence +*left Control*, *left Alt*, *Delete*. This is a convenience wrapper +around [`RFB.sendKey()`](#rfbsendkey). + +##### Syntax + + RFB.sendCtrlAltDel( ); + +#### RFB.machineShutdown() + +The `RFB.machineShutdown()` method is used to request to shut down the +remote machine. The capability `power` must be set for this method to +have any effect. + +##### Syntax + + RFB.machineShutdown( ); + +#### RFB.machineReboot() + +The `RFB.machineReboot()` method is used to request a clean reboot of +the remote machine. The capability `power` must be set for this method +to have any effect. + +##### Syntax + + RFB.machineReboot( ); + +#### RFB.machineReset() + +The `RFB.machineReset()` method is used to request a forced reset of +the remote machine. The capability `power` must be set for this method +to have any effect. + +##### Syntax + + RFB.machineReset( ); + +#### RFB.clipboardPasteFrom() + +The `RFB.clipboardPasteFrom()` method is used to send clipboard data +to the remote server. + +##### Syntax + + RFB.clipboardPasteFrom( text ); + +###### Parameters + +**`text`** + - A `DOMString` specifying the clipboard data to send. Currently only + characters from ISO 8859-1 are supported. + +#### RFB.autoscale() + +The `RFB.autoscale()` method is used to automatically adjust +`RFB.viewportScale` to fit given dimensions. + +##### Syntax + + RFB.autoscale( width, height ); + +###### Parameters + +**`width`** + - A `long` specifying the maximum width of the canvas in CSS pixels. + +**`height`** + - A `long` specifying the maximum height of the canvas in CSS pixels. + +#### RFB.requestDesktopSize() + +The `RFB.requestDesktopSize()` method is used to request a change of +the framebuffer. The capability `resize` must be set for this method to +have any effect. + +Note that this is merely a request and the server may deny it. +The [`fbresize`](#fbresize) event will be fired when the framebuffer +actually changes dimensions. + +##### Syntax + + RFB.requestDesktopSize( width, height ); + +###### Parameters + +**`width`** + - A `long` specifying the new requested width in CSS pixels. + +**`height`** + - A `long` specifying the new requested height in CSS pixels. + +#### RFB.viewportChangeSize() + +The `RFB.viewportChangeSize()` method is used to change the size of the +canvas rather than the underlying framebuffer. + +This method has no effect if `RFB.clipViewport` is set to `false`. + +##### Syntax + + RFB.viewportChangeSize( width, height ); + +###### Parameters + +**`width`** + - A `long` specifying the new width in CSS pixels. + +**`height`** + - A `long` specifying the new height in CSS pixels. diff --git a/tests/playback.js b/tests/playback.js index 745e1f55..2cd74998 100644 --- a/tests/playback.js +++ b/tests/playback.js @@ -77,10 +77,10 @@ export default function RecordingPlayer (frames, encoding, disconnected, notific RecordingPlayer.prototype = { run: function (realtime, trafficManagement) { // initialize a new RFB - this._rfb = new RFB({'target': document.getElementById('VNC_canvas'), - 'view_only': true, - 'onDisconnected': this._handleDisconnect.bind(this), - 'onNotification': this._notification}); + this._rfb = new RFB(document.getElementById('VNC_canvas'), 'wss://test'); + this._rfb.viewOnly = true; + this._rfb.ondisconnected = this._handleDisconnect.bind(this); + this._rfb.onnotification = this._notification; this._enablePlaybackMode(); // reset the frame index and timer @@ -92,9 +92,6 @@ RecordingPlayer.prototype = { this._running = true; - // launch the tests - this._rfb.connect('test', 0, 'bogus'); - this._queueNextPacket(); }, @@ -104,14 +101,8 @@ RecordingPlayer.prototype = { this._rfb._sock.close = function () {}; this._rfb._sock.flush = function () {}; this._rfb._checkEvents = function () {}; - this._rfb.connect = function (host, port, password, path) { - this._rfb_host = host; - this._rfb_port = port; - this._rfb_password = (password !== undefined) ? password : ""; - this._rfb_path = (path !== undefined) ? path : ""; + this._rfb._connect = function () { this._sock.init('binary', 'ws'); - this._rfb_connection_state = 'connecting'; - this._rfb_init_state = 'ProtocolVersion'; }; }, @@ -154,12 +145,12 @@ RecordingPlayer.prototype = { // Avoid having excessive queue buildup in non-realtime mode if (this._trafficManagement && this._rfb._flushing) { let player = this; - let orig = this._rfb._display.get_onFlush(); - this._rfb._display.set_onFlush(function () { - player._rfb._display.set_onFlush(orig); + let orig = this._rfb._display.onflush; + this._rfb._display.onflush = function () { + player._rfb._display.onflush = orig; player._rfb._onFlush(); player._doPacket(); - }); + }; return; } @@ -184,12 +175,12 @@ RecordingPlayer.prototype = { _finish() { if (this._rfb._display.pending()) { var player = this; - this._rfb._display.set_onFlush(function () { + this._rfb._display.onflush = function () { if (player._rfb._flushing) { player._rfb._onFlush(); } player._finish(); - }); + }; this._rfb._display.flush(); } else { this._running = false; diff --git a/tests/test.display.js b/tests/test.display.js index fac0febc..b8e9b51f 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -3,7 +3,6 @@ var expect = chai.expect; import Base64 from '../core/base64.js'; import Display from '../core/display.js'; -import { _forceCursorURIs, browserSupportsCursorURIs } from '../core/util/browsers.js'; import sinon from '../vendor/sinon.js'; @@ -37,30 +36,11 @@ describe('Display/Canvas Helper', function () { return Base64.decode(data); } - describe('checking for cursor uri support', function () { - it('should disable cursor URIs if there is no support', function () { - _forceCursorURIs(false); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); - expect(display._cursor_uri).to.be.false; - }); - - it('should enable cursor URIs if there is support', function () { - _forceCursorURIs(true); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false }); - expect(display._cursor_uri).to.be.true; - }); - - it('respect the cursor_uri option if there is support', function () { - _forceCursorURIs(false); - var display = new Display({ target: document.createElement('canvas'), prefer_js: true, viewport: false, cursor_uri: false }); - expect(display._cursor_uri).to.be.false; - }); - }); - describe('viewport handling', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display = new Display(document.createElement('canvas')); + display.clipViewport = true; display.resize(5, 5); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); @@ -112,18 +92,16 @@ describe('Display/Canvas Helper', function () { }); it('should report clipping when framebuffer > viewport', function () { - var clipping = display.clippingDisplay(); - expect(clipping).to.be.true; + expect(display.isClipped).to.be.true; }); it('should report not clipping when framebuffer = viewport', function () { display.viewportChangeSize(5, 5); - var clipping = display.clippingDisplay(); - expect(clipping).to.be.false; + expect(display.isClipped).to.be.false; }); it('should show the entire framebuffer when disabling the viewport', function() { - display.set_viewport(false); + display.clipViewport = false; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -131,7 +109,7 @@ describe('Display/Canvas Helper', function () { }); it('should ignore viewport changes when the viewport is disabled', function() { - display.set_viewport(false); + display.clipViewport = false; display.viewportChangeSize(2, 2); display.viewportChangePos(1, 1); expect(display.absX(0)).to.equal(0); @@ -141,8 +119,8 @@ describe('Display/Canvas Helper', function () { }); it('should show the entire framebuffer just after enabling the viewport', function() { - display.set_viewport(false); - display.set_viewport(true); + display.clipViewport = false; + display.clipViewport = true; expect(display.absX(0)).to.equal(0); expect(display.absY(0)).to.equal(0); expect(display._target.width).to.equal(5); @@ -153,7 +131,8 @@ describe('Display/Canvas Helper', function () { describe('resizing', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: false }); + display = new Display(document.createElement('canvas')); + display.clipViewport = false; display.resize(4, 4); }); @@ -178,7 +157,7 @@ describe('Display/Canvas Helper', function () { describe('viewport', function () { beforeEach(function () { - display.set_viewport(true); + display.clipViewport = true; display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); }); @@ -214,11 +193,12 @@ describe('Display/Canvas Helper', function () { var canvas; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + canvas = document.createElement('canvas'); + display = new Display(canvas); + display.clipViewport = true; display.resize(4, 4); display.viewportChangeSize(3, 3); display.viewportChangePos(1, 1); - canvas = display.get_target(); document.body.appendChild(canvas); }); @@ -227,21 +207,21 @@ describe('Display/Canvas Helper', function () { }); it('should not change the bitmap size of the canvas', function () { - display.set_scale(2.0); + display.scale = 2.0; expect(canvas.width).to.equal(3); expect(canvas.height).to.equal(3); }); it('should change the effective rendered size of the canvas', function () { - display.set_scale(2.0); + display.scale = 2.0; expect(canvas.clientWidth).to.equal(6); expect(canvas.clientHeight).to.equal(6); }); it('should not change when resizing', function () { - display.set_scale(2.0); + display.scale = 2.0; display.resize(5, 5); - expect(display.get_scale()).to.equal(2.0); + expect(display.scale).to.equal(2.0); expect(canvas.width).to.equal(3); expect(canvas.height).to.equal(3); expect(canvas.clientWidth).to.equal(6); @@ -254,9 +234,10 @@ describe('Display/Canvas Helper', function () { var canvas; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + canvas = document.createElement('canvas'); + display = new Display(canvas); + display.clipViewport = true; display.resize(4, 3); - canvas = display.get_target(); document.body.appendChild(canvas); }); @@ -291,144 +272,125 @@ describe('Display/Canvas Helper', function () { expect(canvas.width).to.equal(4); expect(canvas.height).to.equal(3); }); - - it('should not upscale when downscaleOnly is true', function () { - display.autoscale(2, 2, true); - expect(display.absX(9)).to.equal(18); - expect(display.absY(18)).to.equal(36); - expect(canvas.clientWidth).to.equal(2); - expect(canvas.clientHeight).to.equal(2); - - display.autoscale(16, 9, true); - expect(display.absX(9)).to.equal(9); - expect(display.absY(18)).to.equal(18); - expect(canvas.clientWidth).to.equal(4); - expect(canvas.clientHeight).to.equal(3); - }); }); describe('drawing', function () { // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the // basic cases - function drawing_tests (pref_js) { - var display; - beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: pref_js }); - display.resize(4, 4); - }); + var display; + beforeEach(function () { + display = new Display(document.createElement('canvas')); + display.resize(4, 4); + }); - it('should clear the screen on #clear without a logo set', function () { - display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); - display._logo = null; - display.clear(); - display.resize(4, 4); - var empty = []; - for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } - expect(display).to.have.displayed(new Uint8Array(empty)); - }); + it('should clear the screen on #clear without a logo set', function () { + display.fillRect(0, 0, 4, 4, [0x00, 0x00, 0xff]); + display._logo = null; + display.clear(); + display.resize(4, 4); + var empty = []; + for (var i = 0; i < 4 * display._fb_width * display._fb_height; i++) { empty[i] = 0; } + expect(display).to.have.displayed(new Uint8Array(empty)); + }); - it('should draw the logo on #clear with a logo set', function (done) { - display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; - display.clear(); - display.set_onFlush(function () { - expect(display).to.have.displayed(checked_data); - expect(display._fb_width).to.equal(4); - expect(display._fb_height).to.equal(4); - done(); - }); - display.flush(); - }); - - it('should not draw directly on the target canvas', function () { - display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); - display.flip(); - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - var expected = []; - for (var i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { - expected[i] = 0xff; - expected[i+1] = expected[i+2] = 0; - expected[i+3] = 0xff; - } - expect(display).to.have.displayed(new Uint8Array(expected)); - }); - - it('should support filling a rectangle with particular color via #fillRect', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); - display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); - display.flip(); + it('should draw the logo on #clear with a logo set', function (done) { + display._logo = { width: 4, height: 4, type: "image/png", data: make_image_png(checked_data) }; + display.clear(); + display.onflush = function () { expect(display).to.have.displayed(checked_data); - }); + expect(display._fb_width).to.equal(4); + expect(display._fb_height).to.equal(4); + done(); + }; + display.flush(); + }); - it('should support copying an portion of the canvas via #copyImage', function () { - display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); - display.copyImage(0, 0, 2, 2, 2, 2); - display.flip(); + it('should not draw directly on the target canvas', function () { + display.fillRect(0, 0, 4, 4, [0, 0, 0xff]); + display.flip(); + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + var expected = []; + for (var i = 0; i < 4 * display._fb_width * display._fb_height; i += 4) { + expected[i] = 0xff; + expected[i+1] = expected[i+2] = 0; + expected[i+3] = 0xff; + } + expect(display).to.have.displayed(new Uint8Array(expected)); + }); + + it('should support filling a rectangle with particular color via #fillRect', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0]); + display.fillRect(2, 2, 2, 2, [0xff, 0, 0]); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); + + it('should support copying an portion of the canvas via #copyImage', function () { + display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); + display.fillRect(0, 0, 2, 2, [0xff, 0, 0x00]); + display.copyImage(0, 0, 2, 2, 2, 2); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); + + it('should support drawing images via #imageRect', function (done) { + display.imageRect(0, 0, "image/png", make_image_png(checked_data)); + display.flip(); + display.onflush = function () { expect(display).to.have.displayed(checked_data); - }); + done(); + }; + display.flush(); + }); - it('should support drawing images via #imageRect', function (done) { - display.imageRect(0, 0, "image/png", make_image_png(checked_data)); - display.flip(); - display.set_onFlush(function () { - expect(display).to.have.displayed(checked_data); - done(); - }); - display.flush(); - }); + it('should support drawing tile data with a background color and sub tiles', function () { + display.startTile(0, 0, 4, 4, [0, 0xff, 0]); + display.subTile(0, 0, 2, 2, [0xff, 0, 0]); + display.subTile(2, 2, 2, 2, [0xff, 0, 0]); + display.finishTile(); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing tile data with a background color and sub tiles', function () { - display.startTile(0, 0, 4, 4, [0, 0xff, 0]); - display.subTile(0, 0, 2, 2, [0xff, 0, 0]); - display.subTile(2, 2, 2, 2, [0xff, 0, 0]); - display.finishTile(); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); + it('should support drawing BGRX blit images with true color via #blitImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 4] = checked_data[i * 4 + 2]; + data[i * 4 + 1] = checked_data[i * 4 + 1]; + data[i * 4 + 2] = checked_data[i * 4]; + data[i * 4 + 3] = checked_data[i * 4 + 3]; + } + display.blitImage(0, 0, 4, 4, data, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing BGRX blit images with true color via #blitImage', function () { - var data = []; - for (var i = 0; i < 16; i++) { - data[i * 4] = checked_data[i * 4 + 2]; - data[i * 4 + 1] = checked_data[i * 4 + 1]; - data[i * 4 + 2] = checked_data[i * 4]; - data[i * 4 + 3] = checked_data[i * 4 + 3]; - } - display.blitImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); + it('should support drawing RGB blit images with true color via #blitRgbImage', function () { + var data = []; + for (var i = 0; i < 16; i++) { + data[i * 3] = checked_data[i * 4]; + data[i * 3 + 1] = checked_data[i * 4 + 1]; + data[i * 3 + 2] = checked_data[i * 4 + 2]; + } + display.blitRgbImage(0, 0, 4, 4, data, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); - it('should support drawing RGB blit images with true color via #blitRgbImage', function () { - var data = []; - for (var i = 0; i < 16; i++) { - data[i * 3] = checked_data[i * 4]; - data[i * 3 + 1] = checked_data[i * 4 + 1]; - data[i * 3 + 2] = checked_data[i * 4 + 2]; - } - display.blitRgbImage(0, 0, 4, 4, data, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - - it('should support drawing an image object via #drawImage', function () { - var img = make_image_canvas(checked_data); - display.drawImage(img, 0, 0); - display.flip(); - expect(display).to.have.displayed(checked_data); - }); - } - - describe('(prefering native methods)', function () { drawing_tests.call(this, false); }); - describe('(prefering JavaScript)', function () { drawing_tests.call(this, true); }); + it('should support drawing an image object via #drawImage', function () { + var img = make_image_canvas(checked_data); + display.drawImage(img, 0, 0); + display.flip(); + expect(display).to.have.displayed(checked_data); + }); }); describe('the render queue processor', function () { var display; beforeEach(function () { - display = new Display({ target: document.createElement('canvas'), prefer_js: false }); + display = new Display(document.createElement('canvas')); display.resize(4, 4); sinon.spy(display, '_scan_renderQ'); }); @@ -468,11 +430,11 @@ describe('Display/Canvas Helper', function () { }); it('should call callback when queue is flushed', function () { - display.set_onFlush(sinon.spy()); + display.onflush = sinon.spy(); display.fillRect(0, 0, 4, 4, [0, 0xff, 0]); - expect(display.get_onFlush()).to.not.have.been.called; + expect(display.onflush).to.not.have.been.called; display.flush(); - expect(display.get_onFlush()).to.have.been.calledOnce; + expect(display.onflush).to.have.been.calledOnce; }); it('should draw a blit image on type "blit"', function () { diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js index 7fdeb8f9..513d797f 100644 --- a/tests/test.keyboard.js +++ b/tests/test.keyboard.js @@ -31,105 +31,105 @@ describe('Key Event Handling', function() { describe('Decode Keyboard Events', function() { it('should decode keydown events', function(done) { if (isIE() || isEdge()) this.skip(); - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); }); it('should decode keyup events', function(done) { if (isIE() || isEdge()) this.skip(); var calls = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); if (calls++ === 1) { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); }); describe('Legacy keypress Events', function() { it('should wait for keypress when needed', function() { - var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); it('should decode keypress events', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61})); }); it('should ignore keypress with different code', function() { - var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); it('should handle keypress with missing code', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41})); kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61})); }); it('should guess key if no keypress and numeric key', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x32); expect(code).to.be.equal('Digit2'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'Digit2', keyCode: 0x32})); }); it('should guess key if no keypress and alpha key', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: false})); }); it('should guess key if no keypress and alpha key (with shift)', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x41); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41, shiftKey: true})); }); it('should not guess key if no keypress and unknown key', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x09})); }); }); @@ -139,7 +139,7 @@ describe('Key Event Handling', function() { if (isIE() || isEdge()) this.skip(); }); it('should suppress anything with a valid key', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', key: 'a'}); kbd._handleKeyDown(evt); expect(evt.preventDefault).to.have.been.called; @@ -148,13 +148,13 @@ describe('Key Event Handling', function() { expect(evt.preventDefault).to.have.been.called; }); it('should not suppress keys without key', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); kbd._handleKeyDown(evt); expect(evt.preventDefault).to.not.have.been.called; }); it('should suppress the following keypress event', function() { - var kbd = new Keyboard({}); + var kbd = new Keyboard(document, {}); var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41}); kbd._handleKeyDown(evt); var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41}); @@ -168,8 +168,8 @@ describe('Key Event Handling', function() { it('should fake keyup events for virtual keyboards', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0x61); @@ -182,7 +182,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'})); }); @@ -215,8 +215,8 @@ describe('Key Event Handling', function() { it('should fake keyup events on iOS', function(done) { if (isIE() || isEdge()) this.skip(); var count = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0x61); @@ -229,7 +229,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); }); }); @@ -240,67 +240,67 @@ describe('Key Event Handling', function() { if (isIE() || isEdge()) this.skip(); }); it('should send release using the same keysym as the press', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'})); }); it('should send the same keysym for multiple presses', function() { var count = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('KeyA'); expect(down).to.be.equal(true); count++; - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'})); kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'})); expect(count).to.be.equal(2); }); it('should do nothing on keyup events if no keys are down', function() { - var callback = sinon.spy(); - var kbd = new Keyboard({onKeyEvent: callback}); + var kbd = new Keyboard(document); + kbd.onkeyevent = sinon.spy(); kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'})); - expect(callback).to.not.have.been.called; + expect(kbd.onkeyevent).to.not.have.been.called; }); describe('Legacy Events', function() { it('should track keys using keyCode if no code', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'})); }); it('should ignore compositing code', function() { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Unidentified'); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'})); }); it('should track keys using keyIdentifier if no code', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0x61); expect(code).to.be.equal('Platform65'); if (!down) { done(); } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {keyIdentifier: 'U+0041', key: 'a'})); kbd._handleKeyUp(keyevent('keyup', {keyIdentifier: 'U+0041', key: 'b'})); }); @@ -335,8 +335,8 @@ describe('Key Event Handling', function() { it('should change Alt to AltGraph', function() { var count = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch (count++) { case 0: expect(keysym).to.be.equal(0xFF7E); @@ -347,27 +347,27 @@ describe('Key Event Handling', function() { expect(code).to.be.equal('AltRight'); break; } - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); expect(count).to.be.equal(2); }); it('should change left Super to Alt', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0xFFE9); expect(code).to.be.equal('MetaLeft'); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1})); }); it('should change right Super to left Super', function(done) { - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { expect(keysym).to.be.equal(0xFFEB); expect(code).to.be.equal('MetaRight'); done(); - }}); + }; kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2})); }); }); @@ -400,8 +400,8 @@ describe('Key Event Handling', function() { it('should generate fake undo/redo events on press when AltGraph is down', function() { var times_called = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 0: expect(keysym).to.be.equal(0xFFE3); @@ -439,7 +439,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); @@ -449,8 +449,8 @@ describe('Key Event Handling', function() { }); it('should no do anything on key release', function() { var times_called = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 7: expect(keysym).to.be.equal(0x61); @@ -458,7 +458,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(false); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2})); @@ -469,8 +469,8 @@ describe('Key Event Handling', function() { }); it('should not consider a char modifier to be down on the modifier key itself', function() { var times_called = 0; - var kbd = new Keyboard({ - onKeyEvent: function(keysym, code, down) { + var kbd = new Keyboard(document); + kbd.onkeyevent = function(keysym, code, down) { switch(times_called++) { case 0: expect(keysym).to.be.equal(0xFFE3); @@ -488,7 +488,7 @@ describe('Key Event Handling', function() { expect(down).to.be.equal(true); break; } - }}); + }; // First the modifier combo kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1})); kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1})); diff --git a/tests/test.mouse.js b/tests/test.mouse.js index a67833c7..248a9334 100644 --- a/tests/test.mouse.js +++ b/tests/test.mouse.js @@ -33,59 +33,51 @@ describe('Mouse Event Handling', function() { describe('Decode Mouse Events', function() { it('should decode mousedown events', function(done) { - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - expect(bmask).to.be.equal(0x01); - expect(down).to.be.equal(1); - done(); - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + expect(bmask).to.be.equal(0x01); + expect(down).to.be.equal(1); + done(); + }; mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); }); it('should decode mouseup events', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - expect(bmask).to.be.equal(0x01); - if (calls++ === 1) { - expect(down).to.not.be.equal(1); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + expect(bmask).to.be.equal(0x01); + if (calls++ === 1) { + expect(down).to.not.be.equal(1); + done(); + } + }; mouse._handleMouseDown(mouseevent('mousedown', { button: '0x01' })); mouse._handleMouseUp(mouseevent('mouseup', { button: '0x01' })); }); it('should decode mousemove events', function(done) { - var mouse = new Mouse({ - onMouseMove: function(x, y) { - // Note that target relative coordinates are sent - expect(x).to.be.equal(40); - expect(y).to.be.equal(10); - done(); - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousemove = function(x, y) { + // Note that target relative coordinates are sent + expect(x).to.be.equal(40); + expect(y).to.be.equal(10); + done(); + }; mouse._handleMouseMove(mouseevent('mousemove', { clientX: 50, clientY: 20 })); }); it('should decode mousewheel events', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - calls++; - expect(bmask).to.be.equal(1<<6); - if (calls === 1) { - expect(down).to.be.equal(1); - } else if (calls === 2) { - expect(down).to.not.be.equal(1); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + expect(bmask).to.be.equal(1<<6); + if (calls === 1) { + expect(down).to.be.equal(1); + } else if (calls === 2) { + expect(down).to.not.be.equal(1); + done(); + } + }; mouse._handleMouseWheel(mouseevent('mousewheel', { deltaX: 50, deltaY: 0, deltaMode: 0})); @@ -99,22 +91,20 @@ describe('Mouse Event Handling', function() { it('should use same pos for 2nd tap if close enough', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + done(); + } + }; // touch events are sent in an array of events // with one item for each touch point mouse._handleMouseDown(touchevent( @@ -132,22 +122,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if far apart', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); + } + }; mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); this.clock.tick(10); @@ -163,22 +151,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not soon enough', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); + } + }; mouse._handleMouseDown(touchevent( 'touchstart', { touches: [{ clientX: 78, clientY: 46 }]})); this.clock.tick(10); @@ -194,22 +180,20 @@ describe('Mouse Event Handling', function() { it('should not modify 2nd tap pos if not touch', function(done) { var calls = 0; - var mouse = new Mouse({ - onMouseButton: function(x, y, down, bmask) { - calls++; - if (calls === 1) { - expect(down).to.be.equal(1); - expect(x).to.be.equal(68); - expect(y).to.be.equal(36); - } else if (calls === 3) { - expect(down).to.be.equal(1); - expect(x).to.not.be.equal(68); - expect(y).to.not.be.equal(36); - done(); - } - }, - target: target - }); + var mouse = new Mouse(target); + mouse.onmousebutton = function(x, y, down, bmask) { + calls++; + if (calls === 1) { + expect(down).to.be.equal(1); + expect(x).to.be.equal(68); + expect(y).to.be.equal(36); + } else if (calls === 3) { + expect(down).to.be.equal(1); + expect(x).to.not.be.equal(68); + expect(y).to.not.be.equal(36); + done(); + } + }; mouse._handleMouseDown(mouseevent( 'mousedown', { button: '0x01', clientX: 78, clientY: 46 })); this.clock.tick(10); @@ -231,8 +215,8 @@ describe('Mouse Event Handling', function() { afterEach(function () { this.clock.restore(); }); it('should accumulate wheel events if small enough', function () { - var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -250,7 +234,7 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 4, deltaY: 0, deltaMode: 0 })); - expect(callback).to.have.callCount(2); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up this.clock.tick(10); mouse._handleMouseWheel(mouseevent( @@ -260,12 +244,12 @@ describe('Mouse Event Handling', function() { expect(mouse._accumulatedWheelDeltaX).to.be.equal(4); expect(mouse._accumulatedWheelDeltaY).to.be.equal(9); - expect(callback).to.have.callCount(2); // still + expect(mouse.onmousebutton).to.have.callCount(2); // still }); it('should not accumulate large wheel events', function () { - var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -279,24 +263,24 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 400, deltaY: 400, deltaMode: 0 })); - expect(callback).to.have.callCount(8); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(8); // mouse down and up }); it('should send even small wheel events after a timeout', function () { - var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, deltaX: 1, deltaY: 0, deltaMode: 0 })); this.clock.tick(51); // timeout on 50 ms - expect(callback).to.have.callCount(2); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(2); // mouse down and up }); it('should account for non-zero deltaMode', function () { - var callback = sinon.spy(); - var mouse = new Mouse({ onMouseButton: callback, target: target }); + var mouse = new Mouse(target); + mouse.onmousebutton = sinon.spy(); mouse._handleMouseWheel(mouseevent( 'mousewheel', { clientX: 18, clientY: 40, @@ -308,7 +292,7 @@ describe('Mouse Event Handling', function() { 'mousewheel', { clientX: 18, clientY: 40, deltaX: 1, deltaY: 0, deltaMode: 2 })); - expect(callback).to.have.callCount(4); // mouse down and up + expect(mouse.onmousebutton).to.have.callCount(4); // mouse down and up }); }); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index fa6ba561..4dbe1840 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -9,15 +9,6 @@ import { encodings } from '../core/encodings.js'; import FakeWebSocket from './fake.websocket.js'; import sinon from '../vendor/sinon.js'; -function make_rfb (extra_opts) { - if (!extra_opts) { - extra_opts = {}; - } - - extra_opts.target = extra_opts.target || document.createElement('canvas'); - return new RFB(extra_opts); -} - var push8 = function (arr, num) { "use strict"; arr.push(num & 0xFF); @@ -38,12 +29,13 @@ var push32 = function (arr, num) { }; describe('Remote Frame Buffer Protocol Client', function() { - "use strict"; + var clock; + before(FakeWebSocket.replace); after(FakeWebSocket.restore); before(function () { - this.clock = sinon.useFakeTimers(); + this.clock = clock = sinon.useFakeTimers(); // Use a single set of buffers instead of reallocating to // speed up tests var sock = new Websock(); @@ -63,38 +55,48 @@ describe('Remote Frame Buffer Protocol Client', function() { this.clock.restore(); }); - describe('Public API Basic Behavior', function () { - var client; - beforeEach(function () { - client = make_rfb(); - }); - - describe('#connect', function () { - beforeEach(function () { client._updateConnectionState = sinon.spy(); }); + function make_rfb (url, options) { + url = url || 'wss://host:8675'; + var rfb = new RFB(document.createElement('canvas'), url, options); + clock.tick(); + rfb._sock._websocket._open(); + rfb._rfb_connection_state = 'connected'; + return rfb; + } + describe('Connecting/Disconnecting', function () { + describe('#RFB', function () { it('should set the current state to "connecting"', function () { - client.connect('host', 8675); - expect(client._updateConnectionState).to.have.been.calledOnce; - expect(client._updateConnectionState).to.have.been.calledWith('connecting'); + var client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); + this.clock.tick(); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.state).to.equal('connecting'); }); - it('should not try to connect if we are missing a host', function () { - client._fail = sinon.spy(); - client._rfb_connection_state = ''; - client.connect(undefined, 8675); - expect(client._fail).to.have.been.calledOnce; - expect(client._updateConnectionState).to.not.have.been.called; - expect(client._rfb_connection_state).to.equal(''); + it('should actually connect to the websocket', function () { + var client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH'); + sinon.spy(client._sock, 'open'); + this.clock.tick(); + expect(client._sock.open).to.have.been.calledOnce; + expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); }); }); describe('#disconnect', function () { - beforeEach(function () { client._updateConnectionState = sinon.spy(); }); + var client; + beforeEach(function () { + client = make_rfb(); + }); it('should set the current state to "disconnecting"', function () { + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client.disconnect(); - expect(client._updateConnectionState).to.have.been.calledOnce; - expect(client._updateConnectionState).to.have.been.calledWith('disconnecting'); + expect(spy).to.have.been.calledTwice; + expect(spy.args[0][0].detail.state).to.equal('disconnecting'); + expect(spy.args[1][0].detail.state).to.equal('disconnected'); }); it('should unregister error event handler', function () { @@ -116,33 +118,34 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - describe('#sendPassword', function () { - beforeEach(function () { this.clock = sinon.useFakeTimers(); }); - afterEach(function () { this.clock.restore(); }); + describe('#sendCredentials', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._rfb_connection_state = 'connecting'; + }); - it('should set the rfb password properly"', function () { - client.sendPassword('pass'); - expect(client._rfb_password).to.equal('pass'); + it('should set the rfb credentials properly"', function () { + client.sendCredentials({ password: 'pass' }); + expect(client._rfb_credentials).to.deep.equal({ password: 'pass' }); }); it('should call init_msg "soon"', function () { client._init_msg = sinon.spy(); - client.sendPassword('pass'); + client.sendCredentials({ password: 'pass' }); this.clock.tick(5); expect(client._init_msg).to.have.been.calledOnce; }); }); + }); + + describe('Public API Basic Behavior', function () { + var client; + beforeEach(function () { + client = make_rfb(); + }); describe('#sendCtrlAlDel', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; - }); - it('should sent ctrl[down]-alt[down]-del[down] then del[up]-alt[up]-ctrl[up]', function () { var expected = {_sQ: new Uint8Array(48), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 0xFFE3, 1); @@ -157,28 +160,21 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the keys if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.sendCtrlAltDel(); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the keys if we are set as view_only', function () { - client._view_only = true; + sinon.spy(client._sock, 'flush'); + client._viewOnly = true; client.sendCtrlAltDel(); expect(client._sock.flush).to.not.have.been.called; }); }); describe('#sendKey', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; - }); - it('should send a single key with the given code and state (down = true)', function () { var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(expected, 123, 1); @@ -195,13 +191,15 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the key if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the key if we are set as view_only', function () { - client._view_only = true; + sinon.spy(client._sock, 'flush'); + client._viewOnly = true; client.sendKey(123, 'Key123'); expect(client._sock.flush).to.not.have.been.called; }); @@ -224,15 +222,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('#clipboardPasteFrom', function () { - beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; - }); - it('should send the given text in a paste event', function () { var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}}; RFB.messages.clientCutText(expected, 'abc'); @@ -241,6 +230,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the text if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.clipboardPasteFrom('abc'); expect(client._sock.flush).to.not.have.been.called; @@ -249,12 +239,6 @@ describe('Remote Frame Buffer Protocol Client', function() { describe("#requestDesktopSize", function () { beforeEach(function() { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; client._supportsSetDesktopSize = true; }); @@ -277,12 +261,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should not send the request if the client has not recieved a ExtendedDesktopSize rectangle', function () { + sinon.spy(client._sock, 'flush'); client._supportsSetDesktopSize = false; client.requestDesktopSize(1,2); expect(client._sock.flush).to.not.have.been.called; }); it('should not send the request if we are not in a normal state', function () { + sinon.spy(client._sock, 'flush'); client._rfb_connection_state = "broken"; client.requestDesktopSize(1,2); expect(client._sock.flush).to.not.have.been.called; @@ -291,37 +277,27 @@ describe('Remote Frame Buffer Protocol Client', function() { describe("XVP operations", function () { beforeEach(function () { - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; client._rfb_xvp_ver = 1; }); - it('should send the shutdown signal on #xvpShutdown', function () { - client.xvpShutdown(); + it('should send the shutdown signal on #machineShutdown', function () { + client.machineShutdown(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x02])); }); - it('should send the reboot signal on #xvpReboot', function () { - client.xvpReboot(); + it('should send the reboot signal on #machineReboot', function () { + client.machineReboot(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x03])); }); - it('should send the reset signal on #xvpReset', function () { - client.xvpReset(); + it('should send the reset signal on #machineReset', function () { + client.machineReset(); expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x04])); }); - it('should support sending arbitrary XVP operations via #xvpOp', function () { - client.xvpOp(1, 7); - expect(client._sock).to.have.sent(new Uint8Array([0xFA, 0x00, 0x01, 0x07])); - }); - it('should not send XVP operations with higher versions than we support', function () { - expect(client.xvpOp(2, 7)).to.be.false; + sinon.spy(client._sock, 'flush'); + client._xvpOp(2, 7); expect(client._sock.flush).to.not.have.been.called; }); }); @@ -331,29 +307,24 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('#_updateConnectionState', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); }); - afterEach(function () { - this.clock.restore(); - }); - it('should clear the disconnect timer if the state is not "disconnecting"', function () { var spy = sinon.spy(); client._disconnTimer = setTimeout(spy, 50); - client._updateConnectionState('connecting'); + client._rfb_connection_state = 'connecting'; + client._updateConnectionState('connected'); this.clock.tick(51); expect(spy).to.not.have.been.called; expect(client._disconnTimer).to.be.null; }); it('should call the updateState callback', function () { - client.set_onUpdateState(sinon.spy()); - client._updateConnectionState('connecting'); - var spy = client.get_onUpdateState(); - expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('connecting'); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); + client._updateConnectionState('disconnecting'); + expect(spy.args[0][0].detail.state).to.equal('disconnecting'); }); it('should set the rfb_connection_state', function () { @@ -369,19 +340,19 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should ignore state changes to the same state', function () { - client.set_onUpdateState(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client._rfb_connection_state = 'connecting'; client._updateConnectionState('connecting'); - var spy = client.get_onUpdateState(); expect(spy).to.not.have.been.called; }); it('should ignore illegal state changes', function () { - client.set_onUpdateState(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("updatestate", spy); client._rfb_connection_state = 'connected'; client._updateConnectionState('disconnected'); expect(client._rfb_connection_state).to.not.equal('disconnected'); - var spy = client.get_onUpdateState(); expect(spy).to.not.have.been.called; }); }); @@ -389,13 +360,7 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('#_fail', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); - client.connect('host', 8675); - }); - - afterEach(function () { - this.clock.restore(); }); it('should close the WebSocket connection', function () { @@ -426,13 +391,12 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should result in disconnect callback with message when reason given', function () { client._rfb_connection_state = 'connected'; - client.set_onDisconnected(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._fail('a reason'); - var spy = client.get_onDisconnected(); this.clock.tick(2000); expect(spy).to.have.been.calledOnce; - expect(spy.args[0].length).to.equal(2); - expect(spy.args[0][1]).to.equal('a reason'); + expect(spy.args[0][0].detail.reason).to.equal('a reason'); }); }); @@ -442,89 +406,35 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the notification callback', function () { - client.set_onNotification(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._notification('notify!', 'warn'); - var spy = client.get_onNotification(); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('notify!'); - expect(spy.args[0][2]).to.equal('warn'); + expect(spy.args[0][0].detail.message).to.equal('notify!'); + expect(spy.args[0][0].detail.level).to.equal('warn'); }); it('should not call the notification callback when level is invalid', function () { - client.set_onNotification(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._notification('notify!', 'invalid'); - var spy = client.get_onNotification(); expect(spy).to.not.have.been.called; }); }); }); describe('Connection States', function () { - describe('connecting', function () { - var client; - beforeEach(function () { client = make_rfb(); }); - - it('should reset the variable states', function () { - sinon.spy(client, '_init_vars'); - client._updateConnectionState('connecting'); - expect(client._init_vars).to.have.been.calledOnce; - }); - - it('should actually connect to the websocket', function () { - sinon.spy(client._sock, 'open'); - client._updateConnectionState('connecting'); - expect(client._sock.open).to.have.been.calledOnce; - }); - - it('should use wss:// to connect if encryption is enabled', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client._updateConnectionState('connecting'); - expect(client._sock.open.args[0][0]).to.contain('wss://'); - }); - - it('should use ws:// to connect if encryption is not enabled', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client._updateConnectionState('connecting'); - expect(client._sock.open.args[0][0]).to.contain('wss://'); - }); - - it('should use a uri with the host, port, and path specified to connect', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(false); - client._rfb_host = 'HOST'; - client._rfb_port = 8675; - client._rfb_path = 'PATH'; - client._updateConnectionState('connecting'); - expect(client._sock.open).to.have.been.calledWith('ws://HOST:8675/PATH'); - }); - - it('should not include a port in the uri if not specified in connect', function () { - sinon.spy(client._sock, 'open'); - client.set_encrypt(true); - client.connect('HOST', undefined) - expect(client._sock.open).to.have.been.calledWith('wss://HOST/'); - }); - }); - describe('disconnecting', function () { var client; beforeEach(function () { - this.clock = sinon.useFakeTimers(); client = make_rfb(); - client.connect('host', 8675); - }); - - afterEach(function () { - this.clock.restore(); }); it('should force disconnect if we do not call Websock.onclose within the disconnection timeout', function () { sinon.spy(client, '_updateConnectionState'); client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); - this.clock.tick(client.get_disconnectTimeout() * 1000); + this.clock.tick(3 * 1000); expect(client._updateConnectionState).to.have.been.calledTwice; expect(client._rfb_disconnect_reason).to.not.equal(""); expect(client._rfb_connection_state).to.equal("disconnected"); @@ -532,9 +442,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not fail if Websock.onclose gets called within the disconnection timeout', function () { client._updateConnectionState('disconnecting'); - this.clock.tick(client.get_disconnectTimeout() * 500); + this.clock.tick(3 * 1000 / 2); client._sock._websocket.close(); - this.clock.tick(client.get_disconnectTimeout() * 500 + 1); + this.clock.tick(3 * 1000 / 2 + 1); expect(client._rfb_connection_state).to.equal('disconnected'); }); @@ -550,39 +460,40 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); }); it('should call the disconnect callback if the state is "disconnected"', function () { - client.set_onDisconnected(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = "error"; client._updateConnectionState('disconnected'); - var spy = client.get_onDisconnected(); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal("error"); + expect(spy.args[0][0].detail.reason).to.equal("error"); }); it('should not call the disconnect callback if the state is not "disconnected"', function () { - client.set_onDisconnected(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); + client._sock._websocket.close = function () {}; // explicitly don't call onclose client._updateConnectionState('disconnecting'); - var spy = client.get_onDisconnected(); expect(spy).to.not.have.been.called; }); it('should call the disconnect callback without msg when no reason given', function () { - client.set_onDisconnected(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("disconnect", spy); client._rfb_connection_state = 'disconnecting'; client._rfb_disconnect_reason = ""; client._updateConnectionState('disconnected'); - var spy = client.get_onDisconnected(); expect(spy).to.have.been.calledOnce; expect(spy.args[0].length).to.equal(1); }); it('should call the updateState callback before the disconnect callback', function () { - client.set_onDisconnected(sinon.spy()); - client.set_onUpdateState(sinon.spy()); + var updateStateSpy = sinon.spy(); + var disconnectSpy = sinon.spy(); + client.addEventListener("disconnect", disconnectSpy); + client.addEventListener("updatestate", updateStateSpy); client._rfb_connection_state = 'disconnecting'; client._updateConnectionState('disconnected'); - var updateStateSpy = client.get_onUpdateState(); - var disconnectSpy = client.get_onDisconnected(); expect(updateStateSpy.calledBefore(disconnectSpy)).to.be.true; }); }); @@ -591,15 +502,13 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Protocol Initialization States', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._rfb_connection_state = 'connecting'; + }); + describe('ProtocolVersion', function () { - beforeEach(function () { - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function () { - this.clock.restore(); - }); - function send_ver (ver, client) { var arr = new Uint8Array(12); for (var i = 0; i < ver.length; i++) { @@ -611,22 +520,6 @@ describe('Remote Frame Buffer Protocol Client', function() { } describe('version parsing', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - }); - - it('should interpret version 000.000 as a repeater', function () { - client._repeaterID = '\x01\x02\x03\x04\x05'; - send_ver('000.000', client); - expect(client._rfb_version).to.equal(0); - - var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); - }); - it('should interpret version 003.003 as version 3.3', function () { send_ver('003.003', client); expect(client._rfb_version).to.equal(3.3); @@ -674,26 +567,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - }); - - it('should handle two step repeater negotiation', function () { - client._repeaterID = '\x01\x02\x03\x04\x05'; - - send_ver('000.000', client); - expect(client._rfb_version).to.equal(0); - var sent_data = client._sock._websocket._get_sent_data(); - expect(new Uint8Array(sent_data.buffer, 0, 5)).to.array.equal(new Uint8Array([1, 2, 3, 4, 5])); - expect(sent_data).to.have.length(250); - - send_ver('003.008', client); - expect(client._rfb_version).to.equal(3.8); - }); - it('should send back the interpreted version', function () { send_ver('004.000', client); @@ -710,15 +583,32 @@ describe('Remote Frame Buffer Protocol Client', function() { send_ver('003.008', client); expect(client._rfb_init_state).to.equal('Security'); }); + + describe('Repeater', function () { + beforeEach(function () { + client = make_rfb('wss://host:8675', { repeaterID: "12345" }); + client._rfb_connection_state = 'connecting'; + }); + + it('should interpret version 000.000 as a repeater', function () { + send_ver('000.000', client); + expect(client._rfb_version).to.equal(0); + + var sent_data = client._sock._websocket._get_sent_data(); + expect(new Uint8Array(sent_data.buffer, 0, 9)).to.array.equal(new Uint8Array([73, 68, 58, 49, 50, 51, 52, 53, 0])); + expect(sent_data).to.have.length(250); + }); + + it('should handle two step repeater negotiation', function () { + send_ver('000.000', client); + send_ver('003.008', client); + expect(client._rfb_version).to.equal(3.8); + }); + }); }); describe('Security', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -777,12 +667,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Authentication', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; }); @@ -826,31 +711,27 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('VNC Authentication (type 2) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; }); - it('should call the passwordRequired callback if missing a password', function () { - client.set_onPasswordRequired(sinon.spy()); + it('should fire the credentialsrequired event if missing a password', function () { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); send_security(2, client); var challenge = []; for (var i = 0; i < 16; i++) { challenge[i] = i; } client._sock._websocket._receive_data(new Uint8Array(challenge)); - var spy = client.get_onPasswordRequired(); - expect(client._rfb_password.length).to.equal(0); + expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.types).to.have.members(["password"]); }); it('should encrypt the password with DES and then send it back', function () { - client._rfb_password = 'passwd'; + client._rfb_credentials = { password: 'passwd' }; send_security(2, client); client._sock._websocket._get_sent_data(); // skip the choice of auth reply @@ -863,7 +744,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should transition to SecurityResult immediately after sending the password', function () { - client._rfb_password = 'passwd'; + client._rfb_credentials = { password: 'passwd' }; send_security(2, client); var challenge = []; @@ -875,52 +756,50 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('XVP Authentication (type 22) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; }); it('should fall through to standard VNC authentication upon completion', function () { - client.set_xvp_password_sep('#'); - client._rfb_password = 'user#target#password'; + client._rfb_credentials = { username: 'user', + target: 'target', + password: 'password' }; client._negotiate_std_vnc_auth = sinon.spy(); send_security(22, client); expect(client._negotiate_std_vnc_auth).to.have.been.calledOnce; }); - it('should call the passwordRequired callback if the password is missing', function() { - client.set_onPasswordRequired(sinon.spy()); - client._rfb_password = ''; + it('should fire the credentialsrequired event if all credentials are missing', function() { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); + client._rfb_credentials = {}; send_security(22, client); - var spy = client.get_onPasswordRequired(); - expect(client._rfb_password.length).to.equal(0); + expect(client._rfb_credentials).to.be.empty; expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); }); - it('should call the passwordRequired callback if the password is improperly formatted', function() { - client.set_onPasswordRequired(sinon.spy()); - client._rfb_password = 'user@target'; + it('should fire the credentialsrequired event if some credentials are missing', function() { + var spy = sinon.spy(); + client.addEventListener("credentialsrequired", spy); + client._rfb_credentials = { username: 'user', + target: 'target' }; send_security(22, client); - var spy = client.get_onPasswordRequired(); expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.types).to.have.members(["username", "password", "target"]); }); - it('should split the password, send the first two parts, and pass on the last part', function () { - client.set_xvp_password_sep('#'); - client._rfb_password = 'user#target#password'; + it('should send user and target separately', function () { + client._rfb_credentials = { username: 'user', + target: 'target', + password: 'password' }; client._negotiate_std_vnc_auth = sinon.spy(); send_security(22, client); - expect(client._rfb_password).to.equal('password'); - var expected = [22, 4, 6]; // auth selection, len user, len target for (var i = 0; i < 10; i++) { expected[i+3] = 'usertarget'.charCodeAt(i); } @@ -929,12 +808,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('TightVNC Authentication (type 16) Handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'Security'; client._rfb_version = 3.8; send_security(16, client); @@ -1019,12 +893,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('SecurityResult', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'SecurityResult'; }); @@ -1052,40 +921,33 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('ClientInitialisation', function () { - var client; - - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_init_state = 'SecurityResult'; - }); - it('should transition to the ServerInitialisation state', function () { + var client = make_rfb(); + client._rfb_connection_state = 'connecting'; + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._rfb_init_state).to.equal('ServerInitialisation'); }); it('should send 1 if we are in shared mode', function () { - client.set_shared(true); + var client = make_rfb('wss://host:8675', { shared: true }); + client._rfb_connection_state = 'connecting'; + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([1])); }); it('should send 0 if we are not in shared mode', function () { - client.set_shared(false); + var client = make_rfb('wss://host:8675', { shared: false }); + client._rfb_connection_state = 'connecting'; + client._rfb_init_state = 'SecurityResult'; client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0])); expect(client._sock).to.have.sent(new Uint8Array([0])); }); }); describe('ServerInitialisation', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); client._rfb_init_state = 'ServerInitialisation'; }); @@ -1137,13 +999,13 @@ describe('Remote Frame Buffer Protocol Client', function() { // NB(sross): we just warn, not fail, for endian-ness and shifts, so we don't test them it('should set the framebuffer name and call the callback', function () { - client.set_onDesktopName(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("desktopname", spy); send_server_init({ name: 'some name' }, client); - var spy = client.get_onDesktopName(); expect(client._fb_name).to.equal('some name'); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('some name'); + expect(spy.args[0][0].detail.name).to.equal('some name'); }); it('should handle the extended init message of the tight encoding', function () { @@ -1166,16 +1028,16 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should call the resize callback and resize the display', function () { - client.set_onFBResize(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("fbresize", spy); sinon.spy(client._display, 'resize'); send_server_init({ width: 27, height: 32 }, client); - var spy = client.get_onFBResize(); expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(27, 32); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal(27); - expect(spy.args[0][2]).to.equal(32); + expect(spy.args[0][0].detail.width).to.equal(27); + expect(spy.args[0][0].detail.height).to.equal(32); }); it('should grab the mouse and keyboard', function () { @@ -1240,27 +1102,12 @@ describe('Remote Frame Buffer Protocol Client', function() { beforeEach(function () { client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; client._fb_name = 'some device'; client._fb_width = 640; client._fb_height = 20; }); describe('Framebuffer Update Handling', function () { - var client; - - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - client._fb_width = 640; - client._fb_height = 20; - }); - var target_data_arr = [ 0xff, 0x00, 0x00, 255, 0x00, 0xff, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, 0x00, 0xff, 0x00, 255, 0xff, 0x00, 0x00, 255, 0x00, 0x00, 0xff, 255, 0x00, 0x00, 0xff, 255, @@ -1344,32 +1191,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._sock._websocket._get_sent_data()).to.have.length(0); }); - it('should parse out information from a header before any actual data comes in', function () { - client.set_onFBUReceive(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x02, encodingName: 'RRE' }; - send_fbu_msg([rect_info], [[]], client); - - var spy = client.get_onFBUReceive(); - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, rect_info); - }); - - it('should fire onFBUComplete when the update is complete', function () { - client.set_onFBUComplete(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: -224, encodingName: 'last_rect' }; - send_fbu_msg([rect_info], [[]], client); // last_rect - - var spy = client.get_onFBUComplete(); - expect(spy).to.have.been.calledOnce; - }); - - it('should not fire onFBUComplete if we have not finished processing the update', function () { - client.set_onFBUComplete(sinon.spy()); - var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 0x00, encodingName: 'RAW' }; - send_fbu_msg([rect_info], [[]], client); - expect(client.get_onFBUComplete()).to.not.have.been.called; - }); - it('should fail on an unsupported encoding', function () { sinon.spy(client, "_fail"); var rect_info = { x: 8, y: 11, width: 27, height: 32, encoding: 234 }; @@ -1394,14 +1215,7 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Message Encoding Handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; // a really small frame client._fb_width = 4; client._fb_height = 4; @@ -1481,19 +1295,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('the HEXTILE encoding handler', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - // a really small frame - client._fb_width = 4; - client._fb_height = 4; - client._display.resize(4, 4); - }); - it('should handle a tile with fg, bg specified, normal subrects', function () { var info = [{ x: 0, y: 0, width: 4, height: 4, encoding: 0x05 }]; var rect = []; @@ -1636,13 +1437,14 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle the DesktopSize pseduo-encoding', function () { - client.set_onFBResize(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("fbresize", spy); sinon.spy(client._display, 'resize'); send_fbu_msg([{ x: 0, y: 0, width: 20, height: 50, encoding: -223 }], [[]], client); - var spy = client.get_onFBResize(); expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(spy.args[0][0].detail.width).to.equal(20); + expect(spy.args[0][0].detail.height).to.equal(50); expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); @@ -1652,21 +1454,17 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('the ExtendedDesktopSize pseudo-encoding handler', function () { - var client; + var resizeSpy; beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; client._supportsSetDesktopSize = false; // a really small frame client._fb_width = 4; client._fb_height = 4; client._display.resize(4, 4); sinon.spy(client._display, 'resize'); - client.set_onFBResize(sinon.spy()); + resizeSpy = sinon.spy(); + client.addEventListener("fbresize", resizeSpy); }); function make_screen_data (nr_of_screens) { @@ -1685,6 +1483,26 @@ describe('Remote Frame Buffer Protocol Client', function() { return data; } + it('should call callback when resize is supported', function () { + var spy = sinon.spy(); + client.addEventListener("capabilities", spy); + + expect(client._supportsSetDesktopSize).to.be.false; + expect(client.capabilities.resize).to.be.false; + + var reason_for_change = 0; // server initiated + var status_code = 0; // No error + + send_fbu_msg([{ x: reason_for_change, y: status_code, + width: 4, height: 4, encoding: -308 }], + make_screen_data(1), client); + + expect(client._supportsSetDesktopSize).to.be.true; + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.capabilities.resize).to.be.true; + expect(client.capabilities.resize).to.be.true; + }), + it('should handle a resize requested by this client', function () { var reason_for_change = 1; // requested by this client var status_code = 0; // No error @@ -1693,16 +1511,15 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 20, height: 50, encoding: -308 }], make_screen_data(1), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.get_onFBResize(); - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(20); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should handle a resize requested by another client', function () { @@ -1713,16 +1530,15 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 20, height: 50, encoding: -308 }], make_screen_data(1), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(20); expect(client._fb_height).to.equal(50); expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(20, 50); - var spy = client.get_onFBResize(); - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 20, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(20); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should be able to recieve requests which contain data for multiple screens', function () { @@ -1733,16 +1549,15 @@ describe('Remote Frame Buffer Protocol Client', function() { width: 60, height: 50, encoding: -308 }], make_screen_data(3), client); - expect(client._supportsSetDesktopSize).to.be.true; expect(client._fb_width).to.equal(60); expect(client._fb_height).to.equal(50); expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(60, 50); - var spy = client.get_onFBResize(); - expect(spy).to.have.been.calledOnce; - expect(spy).to.have.been.calledWith(sinon.match.any, 60, 50); + expect(resizeSpy).to.have.been.calledOnce; + expect(resizeSpy.args[0][0].detail.width).to.equal(60); + expect(resizeSpy.args[0][0].detail.height).to.equal(50); }); it('should not handle a failed request', function () { @@ -1758,8 +1573,7 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.not.have.been.called; - var spy = client.get_onFBResize(); - expect(spy).to.not.have.been.called; + expect(resizeSpy).to.not.have.been.called; }); }); @@ -1768,39 +1582,29 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should handle the last_rect pseudo-encoding', function () { - client.set_onFBUReceive(sinon.spy()); send_fbu_msg([{ x: 0, y: 0, width: 0, height: 0, encoding: -224}], [[]], client, 100); expect(client._FBU.rects).to.equal(0); - expect(client.get_onFBUReceive()).to.have.been.calledOnce; }); }); }); describe('XVP Message Handling', function () { - beforeEach(function () { - client = make_rfb(); - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client._fb_name = 'some device'; - client._fb_width = 27; - client._fb_height = 32; - }); - it('should send a notification on XVP_FAIL', function () { - client.set_onNotification(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("notification", spy); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 0])); - var spy = client.get_onNotification(); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal('XVP Operation Failed'); + expect(spy.args[0][0].detail.message).to.equal('XVP Operation Failed'); }); it('should set the XVP version and fire the callback with the version on XVP_INIT', function () { - client.set_onXvpInit(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("capabilities", spy); client._sock._websocket._receive_data(new Uint8Array([250, 0, 10, 1])); expect(client._rfb_xvp_ver).to.equal(10); - expect(client.get_onXvpInit()).to.have.been.calledOnce; - expect(client.get_onXvpInit()).to.have.been.calledWith(10); + expect(spy).to.have.been.calledOnce; + expect(spy.args[0][0].detail.capabilities.power).to.be.true; + expect(client.capabilities.power).to.be.true; }); it('should fail on unknown XVP message types', function () { @@ -1815,18 +1619,19 @@ describe('Remote Frame Buffer Protocol Client', function() { var data = [3, 0, 0, 0]; push32(data, expected_str.length); for (var i = 0; i < expected_str.length; i++) { data.push(expected_str.charCodeAt(i)); } - client.set_onClipboard(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("clipboard", spy); client._sock._websocket._receive_data(new Uint8Array(data)); - var spy = client.get_onClipboard(); expect(spy).to.have.been.calledOnce; - expect(spy.args[0][1]).to.equal(expected_str); + expect(spy.args[0][0].detail.text).to.equal(expected_str); }); it('should fire the bell callback on Bell', function () { - client.set_onBell(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("bell", spy); client._sock._websocket._receive_data(new Uint8Array([2])); - expect(client.get_onBell()).to.have.been.calledOnce; + expect(spy).to.have.been.calledOnce; }); it('should respond correctly to ServerFence', function () { @@ -1899,38 +1704,35 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Asynchronous Events', function () { - describe('Mouse event handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - }); + var client; + beforeEach(function () { + client = make_rfb(); + }); + describe('Mouse event handlers', function () { it('should not send button messages in view-only mode', function () { - client._view_only = true; - client._mouse._onMouseButton(0, 0, 1, 0x001); + client._viewOnly = true; + sinon.spy(client._sock, 'flush'); + client._handleMouseButton(0, 0, 1, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should not send movement messages in view-only mode', function () { - client._view_only = true; - client._mouse._onMouseMove(0, 0); + client._viewOnly = true; + sinon.spy(client._sock, 'flush'); + client._handleMouseMove(0, 0); expect(client._sock.flush).to.not.have.been.called; }); it('should send a pointer event on mouse button presses', function () { - client._mouse._onMouseButton(10, 12, 1, 0x001); + client._handleMouseButton(10, 12, 1, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; 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._mouse._onMouseButton(10, 12, 1, 0x001); + client._handleMouseButton(10, 12, 1, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x001); expect(client._sock).to.have.sent(pointer_msg._sQ); @@ -1938,22 +1740,22 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should send a mask of 0 on mouseup', function () { client._mouse_buttonMask = 0x001; - client._mouse._onMouseButton(10, 12, 0, 0x001); + client._handleMouseButton(10, 12, 0, 0x001); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; 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._mouse._onMouseMove(10, 12); + client._handleMouseMove(10, 12); var pointer_msg = {_sQ: new Uint8Array(6), _sQlen: 0, flush: function () {}}; 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._mouse._onMouseButton(10, 12, 1, 0x010); - client._mouse._onMouseMove(13, 9); + client._handleMouseButton(10, 12, 1, 0x010); + client._handleMouseMove(13, 9); var pointer_msg = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}}; RFB.messages.pointerEvent(pointer_msg, 10, 12, 0x010); RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); @@ -1967,27 +1769,29 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should not send movement messages when viewport dragging', function () { client._viewportDragging = true; client._display.viewportChangePos = sinon.spy(); - client._mouse._onMouseMove(13, 9); + sinon.spy(client._sock, 'flush'); + client._handleMouseMove(13, 9); expect(client._sock.flush).to.not.have.been.called; }); it('should not send button messages when initiating viewport dragging', function () { - client._viewportDrag = true; - client._mouse._onMouseButton(13, 9, 0x001); + client.dragViewport = true; + sinon.spy(client._sock, 'flush'); + client._handleMouseButton(13, 9, 0x001); expect(client._sock.flush).to.not.have.been.called; }); it('should be initiate viewport dragging on a button down event, if enabled', function () { - client._viewportDrag = true; - client._mouse._onMouseButton(13, 9, 0x001); + client.dragViewport = true; + client._handleMouseButton(13, 9, 0x001); expect(client._viewportDragging).to.be.true; expect(client._viewportDragPos).to.deep.equal({ x: 13, y: 9 }); }); it('should terminate viewport dragging on a button up event, if enabled', function () { - client._viewportDrag = true; + client.dragViewport = true; client._viewportDragging = true; - client._mouse._onMouseButton(13, 9, 0x000); + client._handleMouseButton(13, 9, 0x000); expect(client._viewportDragging).to.be.false; }); @@ -1997,13 +1801,13 @@ describe('Remote Frame Buffer Protocol Client', function() { var newX = 123 + 11 * window.devicePixelRatio; var newY = 109 + 4 * window.devicePixelRatio; - client._viewportDrag = true; + client.dragViewport = true; client._viewportDragging = true; client._viewportHasMoved = false; client._viewportDragPos = { x: oldX, y: oldY }; client._display.viewportChangePos = sinon.spy(); - client._mouse._onMouseMove(newX, newY); + client._handleMouseMove(newX, newY); expect(client._viewportDragging).to.be.true; expect(client._viewportHasMoved).to.be.true; @@ -2014,60 +1818,38 @@ describe('Remote Frame Buffer Protocol Client', function() { }); describe('Keyboard Event Handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - client._sock = new Websock(); - client._sock.open('ws://', 'binary'); - client._sock._websocket._open(); - sinon.spy(client._sock, 'flush'); - client._rfb_connection_state = 'connected'; - client._view_only = false; - }); - it('should send a key message on a key press', function () { var keyevent = {}; - client._keyboard._onKeyEvent(0x41, 'KeyA', true); + client._handleKeyEvent(0x41, 'KeyA', true); var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}}; RFB.messages.keyEvent(key_msg, 0x41, 1); expect(client._sock).to.have.sent(key_msg._sQ); }); it('should not send messages in view-only mode', function () { - client._view_only = true; - client._keyboard._onKeyEvent('a', 'KeyA', true); + client._viewOnly = true; + sinon.spy(client._sock, 'flush'); + client._handleKeyEvent('a', 'KeyA', true); expect(client._sock.flush).to.not.have.been.called; }); }); describe('WebSocket event handlers', function () { - var client; - beforeEach(function () { - client = make_rfb(); - this.clock = sinon.useFakeTimers(); - }); - - afterEach(function () { this.clock.restore(); }); - // message events it ('should do nothing if we receive an empty message and have nothing in the queue', function () { - client.connect('host', 8675); - client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([])); expect(client._normal_msg).to.not.have.been.called; }); it('should handle a message in the connected state as a normal message', function () { - client.connect('host', 8675); - client._rfb_connection_state = 'connected'; client._normal_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); expect(client._normal_msg).to.have.been.calledOnce; }); it('should handle a message in any non-disconnected/failed state like an init message', function () { - client.connect('host', 8675); + client._rfb_connection_state = 'connecting'; client._rfb_init_state = 'ProtocolVersion'; client._init_msg = sinon.spy(); client._sock._websocket._receive_data(new Uint8Array([1, 2, 3])); @@ -2075,24 +1857,22 @@ describe('Remote Frame Buffer Protocol Client', function() { }); it('should process all normal messages directly', function () { - client.connect('host', 8675); - client._sock._websocket._open(); - client._rfb_connection_state = 'connected'; - client.set_onBell(sinon.spy()); + var spy = sinon.spy(); + client.addEventListener("bell", spy); client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02])); - expect(client.get_onBell()).to.have.been.calledTwice; + expect(spy).to.have.been.calledTwice; }); // open events it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () { - client.connect('host', 8675); + client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + this.clock.tick(); client._sock._websocket._open(); expect(client._rfb_init_state).to.equal('ProtocolVersion'); }); it('should fail if we are not currently ready to connect and we get an "open" event', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'some_other_state'; client._sock._websocket._open(); expect(client._fail).to.have.been.calledOnce; @@ -2100,7 +1880,6 @@ describe('Remote Frame Buffer Protocol Client', function() { // close events it('should transition to "disconnected" from "disconnecting" on a close event', function () { - client.connect('host', 8675); client._rfb_connection_state = 'disconnecting'; client._sock._websocket.close(); expect(client._rfb_connection_state).to.equal('disconnected'); @@ -2108,7 +1887,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should fail if we get a close event while connecting', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'connecting'; client._sock._websocket.close(); expect(client._fail).to.have.been.calledOnce; @@ -2116,7 +1894,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should fail if we get a close event while disconnected', function () { sinon.spy(client, "_fail"); - client.connect('host', 8675); client._rfb_connection_state = 'disconnected'; client._sock._websocket.close(); expect(client._fail).to.have.been.calledOnce; @@ -2124,7 +1901,6 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should unregister close event handler', function () { sinon.spy(client._sock, 'off'); - client.connect('host', 8675); client._rfb_connection_state = 'disconnecting'; client._sock._websocket.close(); expect(client._sock.off).to.have.been.calledWith('close'); diff --git a/vnc.html b/vnc.html index 40d89c7f..fba59b1b 100644 --- a/vnc.html +++ b/vnc.html @@ -152,18 +152,18 @@ - +
-
+
Power
- - - + + +
@@ -213,7 +213,6 @@ @@ -221,10 +220,6 @@
  • Advanced
      -
    • - -
    • -

    • diff --git a/vnc_lite.html b/vnc_lite.html index 671d78e6..d817885d 100644 --- a/vnc_lite.html +++ b/vnc_lite.html @@ -80,6 +80,7 @@ import RFB from './core/rfb.js'; var rfb; + var doneInitialResize; var resizeTimeout; var desktopName; @@ -92,17 +93,15 @@ rfb.requestDesktopSize(innerW, innerH - controlbarH); } } - function FBUComplete(rfb, fbu) { + function initialResize() { + if (doneInitialResize) return; UIresize(); - rfb.set_onFBUComplete(function() { }); + doneInitialResize = true; } - function updateDesktopName(rfb, name) { - desktopName = name; + function updateDesktopName(e) { + desktopName = e.detail.name; } - function passwordRequired(rfb, msg) { - if (typeof msg === 'undefined') { - msg = 'Password Required: '; - } + function credentials(e) { var html; var form = document.createElement('form'); @@ -114,26 +113,26 @@ document.getElementById('noVNC_status_bar').setAttribute("class", "noVNC_status_warn"); document.getElementById('noVNC_status').innerHTML = ''; document.getElementById('noVNC_status').appendChild(form); - document.getElementById('noVNC_status').querySelector('label').textContent = msg; + document.getElementById('noVNC_status').querySelector('label').textContent = 'Password Required: '; } function setPassword() { - rfb.sendPassword(document.getElementById('password_input').value); + rfb.sendCredentials({ password: document.getElementById('password_input').value }); return false; } function sendCtrlAltDel() { rfb.sendCtrlAltDel(); return false; } - function xvpShutdown() { - rfb.xvpShutdown(); + function machineShutdown() { + rfb.machineShutdown(); return false; } - function xvpReboot() { - rfb.xvpReboot(); + function machineReboot() { + rfb.machineReboot(); return false; } - function xvpReset() { - rfb.xvpReset(); + function machineReset() { + rfb.machineReset(); return false; } function status(text, level) { @@ -148,14 +147,16 @@ document.getElementById('noVNC_status_bar').className = "noVNC_status_" + level; document.getElementById('noVNC_status').textContent = text; } - function updateState(rfb, state, oldstate) { + function updateState(e) { var cad = document.getElementById('sendCtrlAltDelButton'); - switch (state) { + switch (e.detail.state) { case 'connecting': status("Connecting", "normal"); break; case 'connected': - if (rfb && rfb.get_encrypt()) { + doneInitialResize = false; + if (WebUtil.getConfigVar('encrypt', + (window.location.protocol === "https:"))) { status("Connected (encrypted) to " + desktopName, "normal"); } else { @@ -170,25 +171,25 @@ status("Disconnected", "normal"); break; default: - status(state, "warn"); + status(e.detail.state, "warn"); break; } - if (state === 'connected') { + if (e.detail.state === 'connected') { cad.disabled = false; } else { cad.disabled = true; - xvpInit(0); + updatePowerButtons(); } } - function disconnected(rfb, reason) { - if (typeof(reason) !== 'undefined') { - status(reason, "error"); + function disconnect(e) { + if (typeof(e.detail.reason) !== 'undefined') { + status(e.detail.reason, "error"); } } - function notification(rfb, msg, level, options) { - status(msg, level); + function notification(e) { + status(e.detail.message, e.detail.level); } window.onresize = function () { @@ -201,10 +202,10 @@ }, 500); }; - function xvpInit(ver) { - var xvpbuttons; - xvpbuttons = document.getElementById('noVNC_xvp_buttons'); - if (ver >= 1) { + function updatePowerButtons() { + var powerbuttons; + powerbuttons = document.getElementById('noVNC_power_buttons'); + if (rfb.capabilities.power) { xvpbuttons.className= "noVNC_shown"; } else { xvpbuttons.className = "noVNC_hidden"; @@ -212,9 +213,9 @@ } document.getElementById('sendCtrlAltDelButton').onclick = sendCtrlAltDel; - document.getElementById('xvpShutdownButton').onclick = xvpShutdown; - document.getElementById('xvpRebootButton').onclick = xvpReboot; - document.getElementById('xvpResetButton').onclick = xvpReset; + document.getElementById('machineShutdownButton').onclick = machineShutdown; + document.getElementById('machineRebootButton').onclick = machineReboot; + document.getElementById('machineResetButton').onclick = machineReset; WebUtil.init_logging(WebUtil.getConfigVar('logging', 'warn')); document.title = WebUtil.getConfigVar('title', 'noVNC'); @@ -252,27 +253,32 @@ status('Must specify host and port in URL', 'error'); } - try { - rfb = new RFB({'target': document.getElementById('noVNC_canvas'), - 'encrypt': WebUtil.getConfigVar('encrypt', - (window.location.protocol === "https:")), - 'repeaterID': WebUtil.getConfigVar('repeaterID', ''), - 'local_cursor': WebUtil.getConfigVar('cursor', true), - 'shared': WebUtil.getConfigVar('shared', true), - 'view_only': WebUtil.getConfigVar('view_only', false), - 'onNotification': notification, - 'onUpdateState': updateState, - 'onDisconnected': disconnected, - 'onXvpInit': xvpInit, - 'onPasswordRequired': passwordRequired, - 'onFBUComplete': FBUComplete, - 'onDesktopName': updateDesktopName}); - } catch (exc) { - status('Unable to create RFB client -- ' + exc, 'error'); - return; // don't continue trying to connect + var url; + + if (WebUtil.getConfigVar('encrypt', + (window.location.protocol === "https:"))) { + url = 'wss'; + } else { + url = 'ws'; } - rfb.connect(host, port, password, path); + url += '://' + host; + if(port) { + url += ':' + port; + } + url += '/' + path; + + rfb = new RFB(document.getElementById('noVNC_canvas'), url, + { repeaterID: WebUtil.getConfigVar('repeaterID', ''), + shared: WebUtil.getConfigVar('shared', true), + credentials: { password: password } }); + rfb.viewOnly = WebUtil.getConfigVar('view_only', false); + rfb.addEventListener("notification", notification); + rfb.addEventListener("updatestate", updateState); + rfb.addEventListener("disconnect", disconnect); + rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); }); + rfb.addEventListener("credentialsrequired", credentials); + rfb.addEventListener("desktopname", updateDesktopName); })(); @@ -284,13 +290,13 @@
      - + + id="machineShutdownButton"> + id="machineRebootButton"> + id="machineResetButton">