From 9b84f51685243309afb9ca4cefe1de697594705b Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Fri, 24 Nov 2017 15:25:23 +0100 Subject: [PATCH] Move resize handling in to RFB object Makes the API simpler and makes it easier for other frontends to get this functionality. --- app/styles/base.css | 24 -- app/styles/lite.css | 7 - app/ui.js | 150 +---------- core/display.js | 5 - core/rfb.js | 238 ++++++++++++----- docs/API-internal.md | 1 - docs/API.md | 140 ++++------ tests/test.display.js | 9 - tests/test.rfb.js | 589 +++++++++++++++++++++++++++++++----------- vnc.html | 23 +- vnc_lite.html | 36 +-- 11 files changed, 690 insertions(+), 532 deletions(-) diff --git a/app/styles/base.css b/app/styles/base.css index b8ce81b..344db9b 100644 --- a/app/styles/base.css +++ b/app/styles/base.css @@ -854,30 +854,6 @@ select:active { ime-mode: disabled; } -/* HTML5 Canvas */ -#noVNC_screen { - display: flex; - width: 100%; - height: 100%; - overflow: auto; - background-color: rgb(40, 40, 40); -} -:root:not(.noVNC_connected) #noVNC_screen { - display: none; -} - -/* Do not set width/height for VNC_canvas or incorrect - * scaling will occur. Canvas size depends on remote VNC - * settings and noVNC settings. */ -#noVNC_canvas { - margin: auto; - /* IE miscalculates width without this :( */ - flex-shrink: 0; -} -#noVNC_canvas:focus { - outline: none; -} - /*Default noVNC logo.*/ /* From: http://fonts.googleapis.com/css?family=Orbitron:700 */ @font-face { diff --git a/app/styles/lite.css b/app/styles/lite.css index b7df1e3..13e11c7 100644 --- a/app/styles/lite.css +++ b/app/styles/lite.css @@ -61,10 +61,3 @@ html { display: flex; justify-content: flex-end; } - -/* Do not set width/height for VNC_canvas or incorrect - * scaling will occur. Canvas size depends on remote VNC - * settings and noVNC settings. */ -#noVNC_canvas { - margin: auto; -} diff --git a/app/ui.js b/app/ui.js index d9da9a8..3c909cd 100644 --- a/app/ui.js +++ b/app/ui.js @@ -27,7 +27,6 @@ var UI = { connected: false, desktopName: "", - resizeTimeout: null, statusTimeout: null, hideKeyboardTimeout: null, idleControlbarTimeout: null, @@ -87,7 +86,6 @@ var UI = { UI.initFullscreen(); // Setup event handlers - UI.addResizeHandlers(); UI.addControlbarHandlers(); UI.addTouchSpecificHandlers(); UI.addExtraKeysHandlers(); @@ -103,8 +101,6 @@ var UI = { UI.openControlbar(); - UI.updateViewClip(); - UI.updateVisualState('init'); document.documentElement.classList.remove("noVNC_loading"); @@ -205,11 +201,6 @@ var UI = { * EVENT HANDLERS * ------v------*/ - addResizeHandlers: function() { - window.addEventListener('resize', UI.applyResizeMode); - window.addEventListener('resize', UI.updateViewClip); - }, - addControlbarHandlers: function() { document.getElementById("noVNC_control_bar") .addEventListener('mousemove', UI.activateControlbar); @@ -432,7 +423,6 @@ var UI = { UI.disableSetting('port'); UI.disableSetting('path'); UI.disableSetting('repeaterID'); - UI.updateViewClip(); UI.setMouseButton(1); // Hide the controlbar after 2 seconds @@ -1037,7 +1027,7 @@ var UI = { } url += '/' + path; - UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url, + UI.rfb = new RFB(document.getElementById('noVNC_container'), url, { shared: UI.getSetting('shared'), repeaterID: UI.getSetting('repeaterID'), credentials: { password: password } }); @@ -1045,11 +1035,13 @@ var UI = { UI.rfb.addEventListener("disconnect", UI.disconnectFinished); UI.rfb.addEventListener("credentialsrequired", UI.credentials); UI.rfb.addEventListener("securityfailure", UI.securityFailed); - UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); UI.initialResize(); }); + UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); }); UI.rfb.addEventListener("clipboard", UI.clipboardReceive); UI.rfb.addEventListener("bell", UI.bell); - UI.rfb.addEventListener("fbresize", UI.updateSessionSize); UI.rfb.addEventListener("desktopname", UI.updateDesktopName); + UI.rfb.clipViewport = UI.getSetting('view_clip'); + UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale'; + UI.rfb.resizeSession = UI.getSetting('resize') === 'remote'; }, disconnect: function() { @@ -1092,7 +1084,6 @@ var UI = { connectFinished: function (e) { UI.connected = true; UI.inhibit_reconnect = false; - UI.doneInitialResize = false; let msg; if (UI.getSetting('encrypt')) { @@ -1104,7 +1095,7 @@ var UI = { UI.updateVisualState('connected'); // Do this last because it can only be used on rendered elements - document.getElementById('noVNC_canvas').focus(); + UI.rfb.focus(); }, disconnectFinished: function (e) { @@ -1238,74 +1229,8 @@ var UI = { applyResizeMode: function() { if (!UI.rfb) return; - var screen = UI.screenSize(); - - if (screen && UI.connected) { - - var resizeMode = UI.getSetting('resize'); - UI.rfb.viewportScale = 1.0; - - // Make sure the viewport is adjusted first - UI.updateViewClip(); - - if (resizeMode === 'remote') { - - // Request changing the resolution of the remote display to - // the size of the local browser viewport. - - // In order to not send multiple requests before the browser-resize - // is finished we wait 0.5 seconds before sending the request. - clearTimeout(UI.resizeTimeout); - UI.resizeTimeout = setTimeout(function(){ - // Request a remote size covering the viewport - if (UI.rfb.requestDesktopSize(screen.w, screen.h)) { - Log.Debug('Requested new desktop size: ' + - screen.w + 'x' + screen.h); - } - }, 500); - - } else { - UI.updateScaling(); - } - } - }, - - // Re-calculate local scaling - updateScaling: function() { - if (!UI.rfb) return; - - var resizeMode = UI.getSetting('resize'); - if (resizeMode !== 'scale') { - return; - } - - var screen = UI.screenSize(); - - if (!screen || !UI.connected) { - return; - } - - UI.rfb.autoscale(screen.w, screen.h); - UI.fixScrollbars(); - }, - - // Gets the the size of the available viewport in the browser window - screenSize: function() { - var screen = document.getElementById('noVNC_screen'); - return {w: screen.offsetWidth, h: screen.offsetHeight}; - }, - - // 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 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(); - UI.doneInitialResize = true; + UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale'; + UI.rfb.resizeSession = UI.getSetting('resize') === 'remote'; }, /* ------^------- @@ -1314,12 +1239,6 @@ var UI = { * VIEW CLIPPING * ------v------*/ - // Set and configure viewport clipping - setViewClip: function(clip) { - UI.updateSetting('view_clip', clip); - UI.updateViewClip(); - }, - // Update parameters that depend on the viewport clip setting updateViewClip: function() { if (!UI.rfb) return; @@ -1327,11 +1246,7 @@ var UI = { var cur_clip = UI.rfb.clipViewport; var new_clip = UI.getSetting('view_clip'); - var resizeSetting = UI.getSetting('resize'); - if (resizeSetting === 'scale') { - // Disable viewport clipping if we are scaling - new_clip = false; - } else if (isTouchDevice) { + if (isTouchDevice) { // Touch devices usually have shit scrollbars new_clip = true; } @@ -1340,15 +1255,6 @@ var UI = { UI.rfb.clipViewport = new_clip; } - var size = UI.screenSize(); - - if (new_clip && size) { - // When clipping is enabled, the screen is limited to - // the size of the browser window. - UI.rfb.viewportChangeSize(size.w, size.h); - UI.fixScrollbars(); - } - // Changing the viewport may change the state of // the dragging button UI.updateViewDrag(); @@ -1389,23 +1295,13 @@ var UI = { }, updateViewDrag: function() { - var clipping = false; - if (!UI.connected) return; - // Check if viewport drag is possible. It is only possible - // if the remote display is clipping the client display. - if (UI.rfb.clipViewport && UI.rfb.isClipped) { - clipping = true; - } - var viewDragButton = document.getElementById('noVNC_view_drag_button'); - if (!clipping && - 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. + if (!UI.rfb.clipViewport && UI.rfb.dragViewport) { + // We are no longer clipping the viewport. Make sure + // viewport drag isn't active when it can't be used. UI.rfb.dragViewport = false; } @@ -1420,7 +1316,7 @@ var UI = { if (isTouchDevice) { viewDragButton.classList.remove("noVNC_hidden"); - if (clipping) { + if (UI.rfb.clipViewport) { viewDragButton.disabled = false; } else { viewDragButton.disabled = true; @@ -1428,7 +1324,7 @@ var UI = { } else { viewDragButton.disabled = false; - if (clipping) { + if (UI.rfb.clipViewport) { viewDragButton.classList.remove("noVNC_hidden"); } else { viewDragButton.classList.add("noVNC_hidden"); @@ -1703,24 +1599,6 @@ var UI = { WebUtil.init_logging(UI.getSetting('logging')); }, - updateSessionSize: function(e) { - UI.updateViewClip(); - UI.updateScaling(); - UI.fixScrollbars(); - }, - - fixScrollbars: function() { - // This is a hack because Chrome screws up the calculation - // for when scrollbars are needed. So to fix it we temporarily - // toggle them off and on. - var screen = document.getElementById('noVNC_screen'); - screen.style.overflow = 'hidden'; - // Force Chrome to recalculate the layout by asking for - // an element's dimensions - screen.getBoundingClientRect(); - screen.style.overflow = ""; - }, - updateDesktopName: function(e) { UI.desktopName = e.detail.name; // Display the desktop name in the document title diff --git a/core/display.js b/core/display.js index e61802a..b252f99 100644 --- a/core/display.js +++ b/core/display.js @@ -106,11 +106,6 @@ Display.prototype = { 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 ===== diff --git a/core/rfb.js b/core/rfb.js index a5bd843..63c6c6b 100644 --- a/core/rfb.js +++ b/core/rfb.js @@ -66,7 +66,7 @@ export default function RFB(target, url, options) { this._fb_name = ""; - this._capabilities = { power: false, resize: false }; + this._capabilities = { power: false }; this._supportsFence = false; @@ -88,6 +88,7 @@ export default function RFB(target, url, options) { // Timers this._disconnTimer = null; // disconnection timer + this._resizeTimeout = null; // resize rate limiting // Decoder states and stats this._encHandlers = {}; @@ -140,15 +141,29 @@ export default function RFB(target, url, options) { // Bound event handlers this._eventHandlers = { focusCanvas: this._focusCanvas.bind(this), + windowResize: this._windowResize.bind(this), }; // main setup Log.Debug(">> RFB.constructor"); - // Target canvas must be able to have focus - if (!this._target.hasAttribute('tabindex')) { - this._target.tabIndex = -1; - } + // Create DOM elements + this._screen = document.createElement('div'); + this._screen.style.display = 'flex'; + this._screen.style.width = '100%'; + this._screen.style.height = '100%'; + this._screen.style.overflow = 'auto'; + this._screen.style.backgroundColor = 'rgb(40, 40, 40)'; + this._canvas = document.createElement('canvas'); + this._canvas.style.margin = 'auto'; + // Some browsers add an outline on focus + this._canvas.style.outline = 'none'; + // IE miscalculates width without this :( + this._canvas.style.flexShrink = '0'; + this._canvas.width = 0; + this._canvas.height = 0; + this._canvas.tabIndex = -1; + this._screen.appendChild(this._canvas); // populate encHandlers with bound versions this._encHandlers[encodings.encodingRaw] = RFB.encodingHandlers.RAW.bind(this); @@ -166,7 +181,7 @@ export default function RFB(target, url, options) { // NB: nothing that needs explicit teardown should be done // before this point, since this can throw an exception try { - this._display = new Display(this._target); + this._display = new Display(this._canvas); } catch (exc) { Log.Error("Display exception: " + exc); throw exc; @@ -174,10 +189,10 @@ export default function RFB(target, url, options) { this._display.onflush = this._onFlush.bind(this); this._display.clear(); - this._keyboard = new Keyboard(this._target); + this._keyboard = new Keyboard(this._canvas); this._keyboard.onkeyevent = this._handleKeyEvent.bind(this); - this._mouse = new Mouse(this._target); + this._mouse = new Mouse(this._canvas); this._mouse.onmousebutton = this._handleMouseButton.bind(this); this._mouse.onmousemove = this._handleMouseMove.bind(this); @@ -266,13 +281,36 @@ RFB.prototype = { 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; }, + _clipViewport: false, + get clipViewport() { return this._clipViewport; }, + set clipViewport(viewport) { + this._clipViewport = viewport; + this._updateClip(); + }, - get clipViewport() { return this._display.clipViewport; }, - set clipViewport(viewport) { this._display.clipViewport = viewport; }, + _scaleViewport: false, + get scaleViewport() { return this._scaleViewport; }, + set scaleViewport(scale) { + this._scaleViewport = scale; + // Scaling trumps clipping, so we may need to adjust + // clipping when enabling or disabling scaling + if (scale && this._clipViewport) { + this._updateClip(); + } + this._updateScale(); + if (!scale && this._clipViewport) { + this._updateClip(); + } + }, - get isClipped() { return this._display.isClipped; }, + _resizeSession: false, + get resizeSession() { return this._resizeSession; }, + set resizeSession(resize) { + this._resizeSession = resize; + if (resize) { + this._requestRemoteResize(); + } + }, // ===== PUBLIC METHODS ===== @@ -341,38 +379,19 @@ RFB.prototype = { } }, - clipboardPasteFrom: function (text) { - 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); + focus: function () { + this._canvas.focus(); }, - viewportChangeSize: function(width, height) { - if (this._rfb_connection_state !== 'connected') { return; } - this._display.viewportChangeSize(width, height); + blur: function () { + this._canvas.blur(); }, - // 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._viewOnly) { - return; - } - - if (!this._supportsSetDesktopSize) { - return; - } - - RFB.messages.setDesktopSize(this._sock, width, height, - this._screen_id, this._screen_flags); + clipboardPasteFrom: function (text) { + if (this._rfb_connection_state !== 'connected' || this._viewOnly) { return; } + RFB.messages.clientCutText(this._sock, text); }, - // ===== PRIVATE METHODS ===== _connect: function () { @@ -391,20 +410,31 @@ RFB.prototype = { } } + // Make our elements part of the page + this._target.appendChild(this._screen); + + // Monitor size changes of the screen + // FIXME: Use ResizeObserver, or hidden overflow + window.addEventListener('resize', this._eventHandlers.windowResize); + // Always grab focus on some kind of click event - this._target.addEventListener("mousedown", this._eventHandlers.focusCanvas); - this._target.addEventListener("touchstart", this._eventHandlers.focusCanvas); + this._canvas.addEventListener("mousedown", this._eventHandlers.focusCanvas); + this._canvas.addEventListener("touchstart", this._eventHandlers.focusCanvas); Log.Debug("<< RFB.connect"); }, _disconnect: function () { Log.Debug(">> RFB.disconnect"); - this._target.removeEventListener("mousedown", this._eventHandlers.focusCanvas); - this._target.removeEventListener("touchstart", this._eventHandlers.focusCanvas); - this._cleanup(); + this._canvas.removeEventListener("mousedown", this._eventHandlers.focusCanvas); + this._canvas.removeEventListener("touchstart", this._eventHandlers.focusCanvas); + window.removeEventListener('resize', this._eventHandlers.windowResize); + this._keyboard.ungrab(); + this._mouse.ungrab(); this._sock.close(); this._print_stats(); + this._target.removeChild(this._screen); + clearTimeout(this._resizeTimeout); Log.Debug("<< RFB.disconnect"); }, @@ -426,17 +456,6 @@ RFB.prototype = { }); }, - _cleanup: function () { - if (!this._viewOnly) { this._keyboard.ungrab(); } - if (!this._viewOnly) { this._mouse.ungrab(); } - this._display.defaultCursor(); - if (Log.get_logging() !== 'debug') { - // Show noVNC logo when disconnected, unless in - // debug mode - this._display.clear(); - } - }, - _focusCanvas: function(event) { // Respect earlier handlers' request to not do side-effects if (event.defaultPrevented) { @@ -447,7 +466,97 @@ RFB.prototype = { return; } - this._target.focus(); + this.focus(); + }, + + _windowResize: function (event) { + // If the window resized then our screen element might have + // as well. Update the viewport dimensions. + window.requestAnimationFrame(function () { + this._updateClip(); + this._updateScale(); + }.bind(this)); + + if (this._resizeSession) { + // Request changing the resolution of the remote display to + // the size of the local browser viewport. + + // In order to not send multiple requests before the browser-resize + // is finished we wait 0.5 seconds before sending the request. + clearTimeout(this._resizeTimeout); + this._resizeTimeout = setTimeout(this._requestRemoteResize.bind(this), 500); + } + }, + + // Update state of clipping in Display object, and make sure the + // configured viewport matches the current screen size + _updateClip: function () { + var cur_clip = this._display.clipViewport; + var new_clip = this._clipViewport; + + if (this._scaleViewport) { + // Disable viewport clipping if we are scaling + new_clip = false; + } + + if (cur_clip !== new_clip) { + this._display.clipViewport = new_clip; + } + + if (new_clip) { + // When clipping is enabled, the screen is limited to + // the size of the container. + let size = this._screenSize(); + this._display.viewportChangeSize(size.w, size.h); + this._fixScrollbars(); + } + }, + + _updateScale: function () { + if (!this._scaleViewport) { + this._display.scale = 1.0; + } else { + let size = this._screenSize(); + this._display.autoscale(size.w, size.h); + } + this._fixScrollbars(); + }, + + // Requests a change of remote desktop size. This message is an extension + // and may only be sent if we have received an ExtendedDesktopSize message + _requestRemoteResize: function () { + clearTimeout(this._resizeTimeout); + this._resizeTimeout = null; + + if (!this._resizeSession || this._viewOnly || + !this._supportsSetDesktopSize) { + return; + } + + let size = this._screenSize(); + RFB.messages.setDesktopSize(this._sock, size.w, size.h, + this._screen_id, this._screen_flags); + + Log.Debug('Requested new desktop size: ' + + size.w + 'x' + size.h); + }, + + // Gets the the size of the available screen + _screenSize: function () { + return { w: this._screen.offsetWidth, + h: this._screen.offsetHeight }; + }, + + _fixScrollbars: function () { + // This is a hack because Chrome screws up the calculation + // for when scrollbars are needed. So to fix it we temporarily + // toggle them off and on. + var orig = this._screen.style.overflow; + this._screen.style.overflow = 'hidden'; + // Force Chrome to recalculate the layout by asking for + // an element's dimensions + this._screen.getBoundingClientRect(); + this._screen.style.overflow = orig; }, /* @@ -1467,10 +1576,9 @@ RFB.prototype = { this._display.resize(this._fb_width, this._fb_height); - var event = new CustomEvent("fbresize", - { detail: { width: this._fb_width, - height: this._fb_height } }); - this.dispatchEvent(event); + // Adjust the visible viewport based on the new dimensions + this._updateClip(); + this._updateScale(); this._timing.fbu_rt_start = (new Date()).getTime(); this._updateContinuousUpdates(); @@ -2308,8 +2416,16 @@ RFB.encodingHandlers = { this._FBU.bytes = 1; if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; } + var firstUpdate = !this._supportsSetDesktopSize; this._supportsSetDesktopSize = true; - this._setCapability("resize", true); + + // Normally we only apply the current resize mode after a + // window resize event. However there is no such trigger on the + // initial connect. And we don't know if the server supports + // resizing until we've gotten here. + if (firstUpdate) { + this._requestRemoteResize(); + } var number_of_screens = this._sock.rQpeek8(); diff --git a/docs/API-internal.md b/docs/API-internal.md index f030dc3..4943c1a 100644 --- a/docs/API-internal.md +++ b/docs/API-internal.md @@ -89,7 +89,6 @@ None | 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 diff --git a/docs/API.md b/docs/API.md index 4d4f4d5..c5923e3 100644 --- a/docs/API.md +++ b/docs/API.md @@ -23,8 +23,8 @@ protocol stream. `focusOnClick` - Is a `boolean` indicating if keyboard focus should automatically be - moved to the canvas when a `mousedown` or `touchstart` event is - received. + moved to the remote session when a `mousedown` or `touchstart` + event is received. `touchButton` - Is a `long` controlling the button mask that should be simulated @@ -32,24 +32,26 @@ protocol stream. [`MouseEvent.button`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button). Is set to `1` by default. -`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. - `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. + - Is a `boolean` indicating if the remote session should be clipped + to its container. When disabled scrollbars will be shown to handle + the resulting overflow. Disabled by default. `dragViewport` - Is a `boolean` indicating if mouse events should control the - relative position of a clipped canvas. Only relevant if + relative position of a clipped remote session. Only relevant if `clipViewport` is enabled. Disabled by default. -`isClipped` *Read only* - - Is a `boolean` indicating if the framebuffer is larger than the - current canvas, i.e. it is being clipped. +`scaleViewport` + - Is a `boolean` indicating if the remote session should be scaled + locally so it fits its container. When disabled it will be centered + if the remote session is smaller than its container, or handled + according to `clipViewport` if it is larger. Disabled by default. + +`resizeSession` + - Is a `boolean` indicating if a request to resize the remote session + should be sent whenever the container changes dimensions. Disabled + by default. `capabilities` *Read only* - Is an `Object` indicating which optional extensions are available @@ -59,7 +61,6 @@ protocol stream. | name | type | description | -------- | --------- | ----------- | `power` | `boolean` | Machine power control is available - | `resize` | `boolean` | The framebuffer can be resized ### Events @@ -86,9 +87,6 @@ protocol stream. - The `bell` event is fired when a audible bell request is received from the server. -[`fbresize`](#fbresize) - - The `fbresize` event is fired when the framebuffer size is changed. - [`desktopname`](#desktopname) - The `desktopname` event is fired when the remote desktop name changes. @@ -112,6 +110,12 @@ protocol stream. [`RFB.sendCtrlAltDel()`](#rfbsendctrlaltdel) - Send Ctrl-Alt-Del key sequence. +[`RFB.focus()`](#rfbfocus) + - Move keyboard focus to the remote session. + +[`RFB.blur()`](#rfbblur) + - Remove keyboard focus from the remote session. + [`RFB.machineShutdown()`](#rfbmachineshutdown) - Request a shutdown of the remote machine. @@ -124,16 +128,6 @@ protocol stream. [`RFB.clipboardPasteFrom()`](#rfbclipboardPasteFrom) - Send clipboard contents to server. -[`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. - -[`RFB.viewportChangeSize()`](#rfbviewportChangeSize) - - Change size of the viewport. - ### Details #### RFB() @@ -148,9 +142,10 @@ connection to a specified VNC server. ###### 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. + - A block [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) + that specifies where the `RFB` object should attach itself. The + existing contents of the `HTMLElement` will be untouched, but new + elements will be added during the lifetime of the `RFB` object. **`url`** - A `DOMString` specifying the VNC server to connect to. This must be @@ -233,12 +228,6 @@ which is a `DOMString` with the clipboard data. 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 @@ -310,6 +299,25 @@ around [`RFB.sendKey()`](#rfbsendkey). RFB.sendCtrlAltDel( ); +#### RFB.focus() + +The `RFB.focus()` method sets the keyboard focus on the remote session. +Keyboard events will be sent to the remote server after this point. + +##### Syntax + + RFB.focus( ); + +#### RFB.blur() + +The `RFB.blur()` method remove keyboard focus on the remote session. +Keyboard events will no longer be sent to the remote server after this +point. + +##### Syntax + + RFB.blur( ); + #### RFB.machineShutdown() The `RFB.machineShutdown()` method is used to request to shut down the @@ -354,61 +362,3 @@ to the remote server. **`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/test.display.js b/tests/test.display.js index b8e9b51..9e6f049 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -91,15 +91,6 @@ describe('Display/Canvas Helper', function () { expect(display.flip).to.have.been.calledOnce; }); - it('should report clipping when framebuffer > viewport', function () { - expect(display.isClipped).to.be.true; - }); - - it('should report not clipping when framebuffer = viewport', function () { - display.viewportChangeSize(5, 5); - expect(display.isClipped).to.be.false; - }); - it('should show the entire framebuffer when disabling the viewport', function() { display.clipViewport = false; expect(display.absX(0)).to.equal(0); diff --git a/tests/test.rfb.js b/tests/test.rfb.js index 542ce51..31a7f2d 100644 --- a/tests/test.rfb.js +++ b/tests/test.rfb.js @@ -9,6 +9,22 @@ import { encodings } from '../core/encodings.js'; import FakeWebSocket from './fake.websocket.js'; import sinon from '../vendor/sinon.js'; +/* UIEvent constructor polyfill for IE */ +(function () { + if (typeof window.UIEvent === "function") return; + + function UIEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, view: window, detail: undefined }; + var evt = document.createEvent( 'UIEvent' ); + evt.initUIEvent( event, params.bubbles, params.cancelable, params.view, params.detail ); + return evt; + } + + UIEvent.prototype = window.UIEvent.prototype; + + window.UIEvent = UIEvent; +})(); + var push8 = function (arr, num) { "use strict"; arr.push(num & 0xFF); @@ -30,12 +46,16 @@ var push32 = function (arr, num) { describe('Remote Frame Buffer Protocol Client', function() { var clock; + var raf; before(FakeWebSocket.replace); after(FakeWebSocket.restore); before(function () { this.clock = clock = sinon.useFakeTimers(); + // sinon doesn't support this yet + raf = window.requestAnimationFrame; + window.requestAnimationFrame = setTimeout; // Use a single set of buffers instead of reallocating to // speed up tests var sock = new Websock(); @@ -53,12 +73,20 @@ describe('Remote Frame Buffer Protocol Client', function() { after(function () { Websock.prototype._allocate_buffers = Websock.prototype._old_allocate_buffers; this.clock.restore(); + window.requestAnimationFrame = raf; }); + var container; var rfbs; beforeEach(function () { - // Track all created RFB objects + // Create a container element for all RFB objects to attach to + container = document.createElement('div'); + container.style.width = "100%"; + container.style.height = "100%"; + document.body.appendChild(container); + + // And track all created RFB objects rfbs = []; }); afterEach(function () { @@ -69,11 +97,14 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(rfb._disconnect).to.have.been.called; }); rfbs = []; + + document.body.removeChild(container); + container = null; }); function make_rfb (url, options) { url = url || 'wss://host:8675'; - var rfb = new RFB(document.createElement('canvas'), url, options); + var rfb = new RFB(container, url, options); clock.tick(); rfb._sock._websocket._open(); rfb._rfb_connection_state = 'connected'; @@ -85,14 +116,14 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('Connecting/Disconnecting', function () { describe('#RFB', function () { it('should set the current state to "connecting"', function () { - var client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + var client = new RFB(document.createElement('div'), 'wss://host:8675'); client._rfb_connection_state = ''; this.clock.tick(); expect(client._rfb_connection_state).to.equal('connecting'); }); it('should actually connect to the websocket', function () { - var client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH'); + var client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); sinon.spy(client._sock, 'open'); this.clock.tick(); expect(client._sock.open).to.have.been.calledOnce; @@ -239,6 +270,22 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); + describe('#focus', function () { + it('should move focus to canvas object', function () { + client._canvas.focus = sinon.spy(); + client.focus(); + expect(client._canvas.focus).to.have.been.called.once; + }); + }); + + describe('#blur', function () { + it('should remove focus from canvas object', function () { + client._canvas.blur = sinon.spy(); + client.blur(); + expect(client._canvas.blur).to.have.been.called.once; + }); + }); + describe('#clipboardPasteFrom', function () { it('should send the given text in a paste event', function () { var expected = {_sQ: new Uint8Array(11), _sQlen: 0, flush: function () {}}; @@ -255,44 +302,6 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); - describe("#requestDesktopSize", function () { - beforeEach(function() { - client._supportsSetDesktopSize = true; - }); - - it('should send the request with the given width and height', function () { - var expected = [251]; - push8(expected, 0); // padding - push16(expected, 1); // width - push16(expected, 2); // height - push8(expected, 1); // number-of-screens - push8(expected, 0); // padding before screen array - push32(expected, 0); // id - push16(expected, 0); // x-position - push16(expected, 0); // y-position - push16(expected, 1); // width - push16(expected, 2); // height - push32(expected, 0); // flags - - client.requestDesktopSize(1, 2); - expect(client._sock).to.have.sent(new Uint8Array(expected)); - }); - - 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 = "connecting"; - client.requestDesktopSize(1,2); - expect(client._sock.flush).to.not.have.been.called; - }); - }); - describe("XVP operations", function () { beforeEach(function () { client._rfb_xvp_ver = 1; @@ -321,6 +330,394 @@ describe('Remote Frame Buffer Protocol Client', function() { }); }); + describe('Clipping', function () { + var client; + beforeEach(function () { + client = make_rfb(); + container.style.width = '70px'; + container.style.height = '80px'; + client.clipViewport = true; + }); + + it('should update display clip state when changing the property', function () { + var spy = sinon.spy(client._display, "clipViewport", ["set"]); + + client.clipViewport = false; + expect(spy.set).to.have.been.calledOnce; + expect(spy.set).to.have.been.calledWith(false); + spy.set.reset(); + + client.clipViewport = true; + expect(spy.set).to.have.been.calledOnce; + expect(spy.set).to.have.been.calledWith(true); + }); + + it('should update the viewport when the container size changes', function () { + sinon.spy(client._display, "viewportChangeSize"); + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(); + + expect(client._display.viewportChangeSize).to.have.been.calledOnce; + expect(client._display.viewportChangeSize).to.have.been.calledWith(40, 50); + }); + + it('should update the viewport when the remote session resizes', function () { + // Simple ExtendedDesktopSize FBU message + var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x00 ]; + + sinon.spy(client._display, "viewportChangeSize"); + + client._sock._websocket._receive_data(new Uint8Array(incoming)); + + // FIXME: Display implicitly calls viewportChangeSize() when + // resizing the framebuffer, hence calledTwice. + expect(client._display.viewportChangeSize).to.have.been.calledTwice; + expect(client._display.viewportChangeSize).to.have.been.calledWith(70, 80); + }); + + it('should not update the viewport if not clipping', function () { + client.clipViewport = false; + sinon.spy(client._display, "viewportChangeSize"); + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(); + + expect(client._display.viewportChangeSize).to.not.have.been.called; + }); + + it('should not update the viewport if scaling', function () { + client.scaleViewport = true; + sinon.spy(client._display, "viewportChangeSize"); + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(); + + expect(client._display.viewportChangeSize).to.not.have.been.called; + }); + + describe('Dragging', function () { + beforeEach(function () { + client.dragViewport = true; + sinon.spy(RFB.messages, "pointerEvent"); + }); + + afterEach(function () { + RFB.messages.pointerEvent.restore(); + }); + + it('should not send button messages when initiating viewport dragging', function () { + client._handleMouseButton(13, 9, 0x001); + expect(RFB.messages.pointerEvent).to.not.have.been.called; + }); + + it('should send button messages when release without movement', function () { + // Just up and down + client._handleMouseButton(13, 9, 0x001); + client._handleMouseButton(13, 9, 0x000); + expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + + RFB.messages.pointerEvent.reset(); + + // Small movement + client._handleMouseButton(13, 9, 0x001); + client._handleMouseMove(15, 14); + client._handleMouseButton(15, 14, 0x000); + expect(RFB.messages.pointerEvent).to.have.been.calledTwice; + }); + + it('should send button message directly when drag is disabled', function () { + client.dragViewport = false; + client._handleMouseButton(13, 9, 0x001); + expect(RFB.messages.pointerEvent).to.have.been.calledOnce; + }); + + it('should be initiate viewport dragging on sufficient movement', function () { + sinon.spy(client._display, "viewportChangePos"); + + // Too small movement + + client._handleMouseButton(13, 9, 0x001); + client._handleMouseMove(18, 9); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.not.have.been.called; + + // Sufficient movement + + client._handleMouseMove(43, 9); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledWith(-30, 0); + + client._display.viewportChangePos.reset(); + + // Now a small movement should move right away + + client._handleMouseMove(43, 14); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + expect(client._display.viewportChangePos).to.have.been.calledOnce; + expect(client._display.viewportChangePos).to.have.been.calledWith(0, -5); + }); + + it('should not send button messages when dragging ends', function () { + // First the movement + + client._handleMouseButton(13, 9, 0x001); + client._handleMouseMove(43, 9); + client._handleMouseButton(43, 9, 0x000); + + expect(RFB.messages.pointerEvent).to.not.have.been.called; + }); + + it('should terminate viewport dragging on a button up event', function () { + // First the dragging movement + + client._handleMouseButton(13, 9, 0x001); + client._handleMouseMove(43, 9); + client._handleMouseButton(43, 9, 0x000); + + // Another movement now should not move the viewport + + sinon.spy(client._display, "viewportChangePos"); + + client._handleMouseMove(43, 59); + + expect(client._display.viewportChangePos).to.not.have.been.called; + }); + }); + }); + + describe('Scaling', function () { + var client; + beforeEach(function () { + client = make_rfb(); + container.style.width = '70px'; + container.style.height = '80px'; + client.scaleViewport = true; + }); + + it('should update display scale factor when changing the property', function () { + var spy = sinon.spy(client._display, "scale", ["set"]); + sinon.spy(client._display, "autoscale"); + + client.scaleViewport = false; + expect(spy.set).to.have.been.calledOnce; + expect(spy.set).to.have.been.calledWith(1.0); + expect(client._display.autoscale).to.not.have.been.called; + + client.scaleViewport = true; + expect(client._display.autoscale).to.have.been.calledOnce; + expect(client._display.autoscale).to.have.been.calledWith(70, 80); + }); + + it('should update the clipping setting when changing the property', function () { + client.clipViewport = true; + + var spy = sinon.spy(client._display, "clipViewport", ["set"]); + + client.scaleViewport = false; + expect(spy.set).to.have.been.calledOnce; + expect(spy.set).to.have.been.calledWith(true); + + spy.set.reset(); + + client.scaleViewport = true; + expect(spy.set).to.have.been.calledOnce; + expect(spy.set).to.have.been.calledWith(false); + }); + + it('should update the scaling when the container size changes', function () { + sinon.spy(client._display, "autoscale"); + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(); + + expect(client._display.autoscale).to.have.been.calledOnce; + expect(client._display.autoscale).to.have.been.calledWith(40, 50); + }); + + it('should update the scaling when the remote session resizes', function () { + // Simple ExtendedDesktopSize FBU message + var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xfe, 0xcc, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, + 0x00, 0x00, 0x00, 0x00 ]; + + sinon.spy(client._display, "autoscale"); + + client._sock._websocket._receive_data(new Uint8Array(incoming)); + + expect(client._display.autoscale).to.have.been.calledOnce; + expect(client._display.autoscale).to.have.been.calledWith(70, 80); + }); + + it('should not update the display scale factor if not scaling', function () { + client.scaleViewport = false; + + sinon.spy(client._display, "autoscale"); + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(); + + expect(client._display.autoscale).to.not.have.been.called; + }); + }); + + describe('Remote resize', function () { + var client; + beforeEach(function () { + client = make_rfb(); + client._supportsSetDesktopSize = true; + client.resizeSession = true; + container.style.width = '70px'; + container.style.height = '80px'; + sinon.spy(RFB.messages, "setDesktopSize"); + }); + + afterEach(function () { + RFB.messages.setDesktopSize.restore(); + }); + + it('should only request a resize when turned on', function () { + client.resizeSession = false; + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + client.resizeSession = true; + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + }); + + it('should request a resize when initially connecting', function () { + // Simple ExtendedDesktopSize FBU message + var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00 ]; + + // First message should trigger a resize + + client._supportsSetDesktopSize = false; + + client._sock._websocket._receive_data(new Uint8Array(incoming)); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 70, 80, 0, 0); + + RFB.messages.setDesktopSize.reset(); + + // Second message should not trigger a resize + + client._sock._websocket._receive_data(new Uint8Array(incoming)); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should request a resize when the container resizes', function () { + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0); + }); + + it('should not resize until the container size is stable', function () { + container.style.width = '20px'; + container.style.height = '30px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(400); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(400); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + + clock.tick(200); + + expect(RFB.messages.setDesktopSize).to.have.been.calledOnce; + expect(RFB.messages.setDesktopSize).to.have.been.calledWith(sinon.match.object, 40, 50, 0, 0); + }); + + it('should not resize when resize is disabled', function () { + client._resizeSession = false; + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should not resize when resize is not supported', function () { + client._supportsSetDesktopSize = false; + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should not resize when in view only mode', function () { + client._viewOnly = true; + + container.style.width = '40px'; + container.style.height = '50px'; + var event = new UIEvent('resize'); + window.dispatchEvent(event); + clock.tick(1000); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + + it('should not try to override a server resize', function () { + // Simple ExtendedDesktopSize FBU message + var incoming = [ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x04, 0xff, 0xff, 0xfe, 0xcc, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00 ]; + + client._sock._websocket._receive_data(new Uint8Array(incoming)); + + expect(RFB.messages.setDesktopSize).to.not.have.been.called; + }); + }); + describe('Misc Internals', function () { describe('#_updateConnectionState', function () { var client; @@ -421,7 +818,7 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('Connection States', function () { describe('connecting', function () { it('should open the websocket connection', function () { - var client = new RFB(document.createElement('canvas'), + var client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); sinon.spy(client._sock, 'open'); this.clock.tick(); @@ -494,7 +891,7 @@ describe('Remote Frame Buffer Protocol Client', function() { describe('disconnected', function () { var client; beforeEach(function () { - client = new RFB(document.createElement('canvas'), 'ws://HOST:8675/PATH'); + client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH'); }); it('should result in a disconnect event if state becomes "disconnected"', function () { @@ -1082,17 +1479,12 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._rfb_connection_state).to.equal('connected'); }); - it('should call the resize callback and resize the display', function () { - var spy = sinon.spy(); - client.addEventListener("fbresize", spy); + it('should resize the display', function () { sinon.spy(client._display, 'resize'); send_server_init({ width: 27, height: 32 }, client); 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][0].detail.width).to.equal(27); - expect(spy.args[0][0].detail.height).to.equal(32); }); it('should grab the mouse and keyboard', function () { @@ -1493,14 +1885,9 @@ describe('Remote Frame Buffer Protocol Client', function() { it('should handle the DesktopSize pseduo-encoding', function () { 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); - expect(spy).to.have.been.calledOnce; - 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); @@ -1512,14 +1899,12 @@ describe('Remote Frame Buffer Protocol Client', function() { var resizeSpy; beforeEach(function () { - 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'); resizeSpy = sinon.spy(); - client.addEventListener("fbresize", resizeSpy); }); function make_screen_data (nr_of_screens) { @@ -1538,26 +1923,6 @@ 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 @@ -1571,10 +1936,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(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 () { @@ -1590,10 +1951,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(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 () { @@ -1609,10 +1966,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._display.resize).to.have.been.calledOnce; expect(client._display.resize).to.have.been.calledWith(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 () { @@ -1627,8 +1980,6 @@ describe('Remote Frame Buffer Protocol Client', function() { expect(client._fb_height).to.equal(4); expect(client._display.resize).to.not.have.been.called; - - expect(resizeSpy).to.not.have.been.called; }); }); @@ -1808,60 +2159,6 @@ describe('Remote Frame Buffer Protocol Client', function() { RFB.messages.pointerEvent(pointer_msg, 13, 9, 0x010); expect(client._sock).to.have.sent(pointer_msg._sQ); }); - - // NB(directxman12): we don't need to test not sending messages in - // non-normal modes, since we haven't grabbed input - // yet (grabbing input should be checked in the lifecycle tests). - - it('should not send movement messages when viewport dragging', function () { - client._viewportDragging = true; - client._display.viewportChangePos = sinon.spy(); - 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.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.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.dragViewport = true; - client._viewportDragging = true; - client._handleMouseButton(13, 9, 0x000); - expect(client._viewportDragging).to.be.false; - }); - - it('if enabled, viewportDragging should occur on mouse movement while a button is down', function () { - var oldX = 123; - var oldY = 109; - var newX = 123 + 11 * window.devicePixelRatio; - var newY = 109 + 4 * window.devicePixelRatio; - - client.dragViewport = true; - client._viewportDragging = true; - client._viewportHasMoved = false; - client._viewportDragPos = { x: oldX, y: oldY }; - client._display.viewportChangePos = sinon.spy(); - - client._handleMouseMove(newX, newY); - - expect(client._viewportDragging).to.be.true; - expect(client._viewportHasMoved).to.be.true; - expect(client._viewportDragPos).to.deep.equal({ x: newX, y: newY }); - expect(client._display.viewportChangePos).to.have.been.calledOnce; - expect(client._display.viewportChangePos).to.have.been.calledWith(oldX - newX, oldY - newY); - }); }); describe('Keyboard Event Handlers', function () { @@ -1912,7 +2209,7 @@ describe('Remote Frame Buffer Protocol Client', function() { // open events it('should update the state to ProtocolVersion on open (if the state is "connecting")', function () { - client = new RFB(document.createElement('canvas'), 'wss://host:8675'); + client = new RFB(document.createElement('div'), 'wss://host:8675'); this.clock.tick(); client._sock._websocket._open(); expect(client._rfb_init_state).to.equal('ProtocolVersion'); diff --git a/vnc.html b/vnc.html index fba59b1..9c945ef 100644 --- a/vnc.html +++ b/vnc.html @@ -315,22 +315,15 @@
+
- -
- - - - - Canvas not supported. - -
- + +