summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2017-12-11 16:48:54 +0100
committerPierre Ossman <ossman@cendio.se>2017-12-11 16:48:54 +0100
commit4f4f62261af9006b4d7c3bd3156bb1a869b0d96c (patch)
tree16ab480f6ee2477059d7daa6a97cc8bf5886b0cc
parentd15dd55e7be829285a79d8cdeb99ef430f13f523 (diff)
parentbd2ce0ca89b0385576b428a8da8de0589bddc097 (diff)
downloadnovnc-4f4f62261af9006b4d7c3bd3156bb1a869b0d96c.tar.gz
Merge branch 'resize' of https://github.com/CendioOssman/noVNC
-rw-r--r--app/styles/base.css24
-rw-r--r--app/styles/lite.css7
-rw-r--r--app/ui.js150
-rw-r--r--core/display.js5
-rw-r--r--core/rfb.js256
-rw-r--r--docs/API-internal.md1
-rw-r--r--docs/API.md140
-rw-r--r--tests/test.display.js9
-rw-r--r--tests/test.rfb.js668
-rw-r--r--vnc.html23
-rw-r--r--vnc_lite.html37
11 files changed, 753 insertions, 567 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 4115745..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;
},
/*
@@ -634,18 +743,26 @@ RFB.prototype = {
if (down && !this._viewportDragging) {
this._viewportDragging = true;
this._viewportDragPos = {'x': x, 'y': y};
+ this._viewportHasMoved = false;
// Skip sending mouse events
return;
} else {
this._viewportDragging = false;
- // 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._viewOnly) {
- RFB.messages.pointerEvent(this._sock, this._display.absX(x), this._display.absY(y), bmask);
+ // If we actually performed a drag then we are done
+ // here and should not send any mouse events
+ if (this._viewportHasMoved) {
+ return;
}
- this._viewportHasMoved = false;
+
+ // Otherwise we treat this as a mouse click event.
+ // Send the button down event here, as the button up
+ // event is sent at the end of this function.
+ RFB.messages.pointerEvent(this._sock,
+ this._display.absX(x),
+ this._display.absY(y),
+ bmask);
}
}
@@ -1459,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();
@@ -2300,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 81ee1dd..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,28 +73,57 @@ 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 () {
+ // 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 () {
+ // Make sure every created RFB object is properly cleaned up
+ // or they might affect subsequent tests
+ rfbs.forEach(function (rfb) {
+ rfb.disconnect();
+ 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';
+ sinon.spy(rfb, "_disconnect");
+ rfbs.push(rfb);
return rfb;
}
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;
@@ -161,7 +210,7 @@ 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._rfb_connection_state = "connecting";
client.sendCtrlAltDel();
expect(client._sock.flush).to.not.have.been.called;
});
@@ -192,7 +241,7 @@ 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._rfb_connection_state = "connecting";
client.sendKey(123, 'Key123');
expect(client._sock.flush).to.not.have.been.called;
});
@@ -221,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 () {}};
@@ -231,50 +296,12 @@ 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._rfb_connection_state = "connecting";
client.clipboardPasteFrom('abc');
expect(client._sock.flush).to.not.have.been.called;
});
});
- 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 = "broken";
- client.requestDesktopSize(1,2);
- expect(client._sock.flush).to.not.have.been.called;
- });
- });
-
describe("XVP operations", function () {
beforeEach(function () {
client._rfb_xvp_ver = 1;
@@ -303,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;
@@ -321,28 +736,32 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should set the rfb_connection_state', function () {
- client._rfb_connection_state = 'disconnecting';
- client._updateConnectionState('disconnected');
- expect(client._rfb_connection_state).to.equal('disconnected');
+ client._rfb_connection_state = 'connecting';
+ client._updateConnectionState('connected');
+ expect(client._rfb_connection_state).to.equal('connected');
});
it('should not change the state when we are disconnected', function () {
- client._rfb_connection_state = 'disconnected';
+ client.disconnect();
+ expect(client._rfb_connection_state).to.equal('disconnected');
client._updateConnectionState('connecting');
expect(client._rfb_connection_state).to.not.equal('connecting');
});
it('should ignore state changes to the same state', function () {
var connectSpy = sinon.spy();
- var disconnectSpy = sinon.spy();
client.addEventListener("connect", connectSpy);
- client.addEventListener("disconnect", disconnectSpy);
- client._rfb_connection_state = 'connected';
+ expect(client._rfb_connection_state).to.equal('connected');
client._updateConnectionState('connected');
expect(connectSpy).to.not.have.been.called;
- client._rfb_connection_state = 'disconnected';
+ client.disconnect();
+
+ var disconnectSpy = sinon.spy();
+ client.addEventListener("disconnect", disconnectSpy);
+
+ expect(client._rfb_connection_state).to.equal('disconnected');
client._updateConnectionState('disconnected');
expect(disconnectSpy).to.not.have.been.called;
});
@@ -350,7 +769,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should ignore illegal state changes', function () {
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
- client._rfb_connection_state = 'connected';
client._updateConnectionState('disconnected');
expect(client._rfb_connection_state).to.not.equal('disconnected');
expect(spy).to.not.have.been.called;
@@ -400,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();
@@ -460,11 +878,21 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._updateConnectionState('disconnecting');
expect(client._sock.close).to.have.been.calledOnce;
});
+
+ it('should not result in a disconnect event', function () {
+ var spy = sinon.spy();
+ client.addEventListener("disconnect", spy);
+ client._sock._websocket.close = function () {}; // explicitly don't call onclose
+ client._updateConnectionState('disconnecting');
+ expect(spy).to.not.have.been.called;
+ });
});
describe('disconnected', function () {
var client;
- beforeEach(function () { client = make_rfb(); });
+ beforeEach(function () {
+ client = new RFB(document.createElement('div'), 'ws://HOST:8675/PATH');
+ });
it('should result in a disconnect event if state becomes "disconnected"', function () {
var spy = sinon.spy();
@@ -475,14 +903,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(spy.args[0][0].detail.clean).to.be.true;
});
- it('should not result in a disconnect event if the state is not "disconnected"', function () {
- var spy = sinon.spy();
- client.addEventListener("disconnect", spy);
- client._sock._websocket.close = function () {}; // explicitly don't call onclose
- client._updateConnectionState('disconnecting');
- expect(spy).to.not.have.been.called;
- });
-
it('should result in a disconnect event without msg when no reason given', function () {
var spy = sinon.spy();
client.addEventListener("disconnect", spy);
@@ -892,7 +1312,6 @@ describe('Remote Frame Buffer Protocol Client', function() {
});
it('should fall through to ServerInitialisation on a response code of 0', function () {
- client._updateConnectionState = sinon.spy();
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 0]));
expect(client._rfb_init_state).to.equal('ServerInitialisation');
});
@@ -1060,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 () {
@@ -1471,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);
@@ -1490,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) {
@@ -1516,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
@@ -1549,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 () {
@@ -1568,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 () {
@@ -1587,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 () {
@@ -1605,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;
});
});
@@ -1786,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 () {
@@ -1890,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');
@@ -1898,14 +2217,18 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should fail if we are not currently ready to connect and we get an "open" event', function () {
sinon.spy(client, "_fail");
- client._rfb_connection_state = 'some_other_state';
+ client._rfb_connection_state = 'connected';
client._sock._websocket._open();
expect(client._fail).to.have.been.calledOnce;
});
// close events
it('should transition to "disconnected" from "disconnecting" on a close event', function () {
- client._rfb_connection_state = 'disconnecting';
+ var real = client._sock._websocket.close;
+ client._sock._websocket.close = function () {};
+ client.disconnect();
+ expect(client._rfb_connection_state).to.equal('disconnecting');
+ client._sock._websocket.close = real;
client._sock._websocket.close();
expect(client._rfb_connection_state).to.equal('disconnected');
});
@@ -1917,16 +2240,9 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._fail).to.have.been.calledOnce;
});
- it('should fail if we get a close event while disconnected', function () {
- sinon.spy(client, "_fail");
- client._rfb_connection_state = 'disconnected';
- client._sock._websocket.close();
- expect(client._fail).to.have.been.calledOnce;
- });
-
it('should unregister close event handler', function () {
sinon.spy(client._sock, 'off');
- client._rfb_connection_state = 'disconnecting';
+ client.disconnect();
client._sock._websocket.close();
expect(client._sock.off).to.have.been.calledWith('close');
});
diff --git a/vnc.html b/vnc.html
index fba59b1..9c945ef 100644
--- a/vnc.html
+++ b/vnc.html
@@ -315,22 +315,15 @@
<div class="noVNC_spinner"></div>
</div>
+ <!-- This is where the RFB elements will attach -->
<div id="noVNC_container">
- <!-- HTML5 Canvas -->
- <div id="noVNC_screen">
- <!-- Note that Google Chrome on Android doesn't respect any of these,
- html attributes which attempt to disable text suggestions on the
- on-screen keyboard. Let's hope Chrome implements the ime-mode
- style for example -->
- <textarea id="noVNC_keyboardinput" autocapitalize="off"
- autocorrect="off" autocomplete="off" spellcheck="false"
- mozactionhint="Enter" tabindex="-1"></textarea>
-
- <canvas id="noVNC_canvas" width="0" height="0">
- Canvas not supported.
- </canvas>
- </div>
-
+ <!-- Note that Google Chrome on Android doesn't respect any of these,
+ html attributes which attempt to disable text suggestions on the
+ on-screen keyboard. Let's hope Chrome implements the ime-mode
+ style for example -->
+ <textarea id="noVNC_keyboardinput" autocapitalize="off"
+ autocorrect="off" autocomplete="off" spellcheck="false"
+ mozactionhint="Enter" tabindex="-1"></textarea>
</div>
<audio id="noVNC_bell">
diff --git a/vnc_lite.html b/vnc_lite.html
index d5a5874..e00f96e 100644
--- a/vnc_lite.html
+++ b/vnc_lite.html
@@ -80,24 +80,8 @@
import RFB from './core/rfb.js';
var rfb;
- var doneInitialResize;
- var resizeTimeout;
var desktopName;
- function UIresize() {
- if (WebUtil.getConfigVar('resize', false)) {
- var innerW = window.innerWidth;
- var innerH = window.innerHeight;
- var controlbarH = document.getElementById('noVNC_status_bar').offsetHeight;
- if (innerW !== undefined && innerH !== undefined)
- rfb.requestDesktopSize(innerW, innerH - controlbarH);
- }
- }
- function initialResize() {
- if (doneInitialResize) return;
- UIresize();
- doneInitialResize = true;
- }
function updateDesktopName(e) {
desktopName = e.detail.name;
}
@@ -150,7 +134,6 @@
function connected(e) {
document.getElementById('sendCtrlAltDelButton').disabled = false;
- doneInitialResize = false;
if (WebUtil.getConfigVar('encrypt',
(window.location.protocol === "https:"))) {
status("Connected (encrypted) to " + desktopName, "normal");
@@ -169,16 +152,6 @@
}
}
- window.onresize = function () {
- // When the window has been resized, wait until the size remains
- // the same for 0.5 seconds before sending the request for changing
- // the resolution of the session
- clearTimeout(resizeTimeout);
- resizeTimeout = setTimeout(function(){
- UIresize();
- }, 500);
- };
-
function updatePowerButtons() {
var powerbuttons;
powerbuttons = document.getElementById('noVNC_power_buttons');
@@ -247,16 +220,18 @@
}
url += '/' + path;
- rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+ rfb = new RFB(document.body, url,
{ repeaterID: WebUtil.getConfigVar('repeaterID', ''),
shared: WebUtil.getConfigVar('shared', true),
credentials: { password: password } });
rfb.viewOnly = WebUtil.getConfigVar('view_only', false);
rfb.addEventListener("connect", connected);
rfb.addEventListener("disconnect", disconnected);
- rfb.addEventListener("capabilities", function () { updatePowerButtons(); initialResize(); });
+ rfb.addEventListener("capabilities", function () { updatePowerButtons(); });
rfb.addEventListener("credentialsrequired", credentials);
rfb.addEventListener("desktopname", updateDesktopName);
+ rfb.scaleViewport = WebUtil.getConfigVar('scale', false);
+ rfb.resizeSession = WebUtil.getConfigVar('resize', false);
})();
</script>
</head>
@@ -278,9 +253,5 @@
</span>
</div>
</div>
- <canvas id="noVNC_canvas" width="0" height="0">
- Canvas not supported.
- </canvas>
-
</body>
</html>