From 8b46c0deb027f06b9db0601d518d1b1490ece66c Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 17 Feb 2015 18:53:38 -0500 Subject: [PATCH 1/2] Update UI to allow for different scaling modes This commit updates the UI to allow for different scaling modes. The "resize" option was changed to be a dropdown with the following options: "None" (nothing), "Remote Resizing" (SetDesktopSize). --- include/ui.js | 4 ++-- vnc.html | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/ui.js b/include/ui.js index e923ea8c..57e630bd 100644 --- a/include/ui.js +++ b/include/ui.js @@ -46,7 +46,7 @@ var UI; }, onresize: function (callback) { - if (UI.getSetting('resize')) { + if (UI.getSetting('resize') === 'remote') { var innerW = window.innerWidth; var innerH = window.innerHeight; var controlbarH = $D('noVNC-control-bar').offsetHeight; @@ -104,7 +104,7 @@ var UI; UI.initSetting('encrypt', (window.location.protocol === "https:")); UI.initSetting('true_color', true); UI.initSetting('cursor', !UI.isTouchDevice); - UI.initSetting('resize', false); + UI.initSetting('resize', 'off'); UI.initSetting('shared', true); UI.initSetting('view_only', false); UI.initSetting('path', 'websockify'); diff --git a/vnc.html b/vnc.html index 7cc07cf9..1d1abaaf 100644 --- a/vnc.html +++ b/vnc.html @@ -157,10 +157,16 @@
  • True Color
  • Local Cursor
  • Clip to Window
  • -
  • Resize Remote to Window
  • Shared Mode
  • View Only
  • +
  • Path
  • +
  • +
  • Repeater ID

  • From 72747869a7a95b72d26914508ce8ec5670eecc5b Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Tue, 17 Feb 2015 22:41:34 -0500 Subject: [PATCH 2/2] Support local scaling This commit adds two new addition scaling options. Both options do local scaling. The first "Local Scaling", does both upscaling and downscaling. The second option, "Local Downscaling", only downscales. This is based on work by @mightypenguin (with an additional bug reported by @glazik12). --- include/display.js | 68 +++++++++++++++++++++---------------- include/ui.js | 61 +++++++++++++++++++++++++++------ include/util.js | 10 ++++-- tests/test.display.js | 78 +++++++++++++++++++++++++++++++++++++++++++ vnc.html | 2 ++ 5 files changed, 176 insertions(+), 43 deletions(-) diff --git a/include/display.js b/include/display.js index d1278681..2b1b827b 100644 --- a/include/display.js +++ b/include/display.js @@ -518,38 +518,48 @@ var Display; return this._fb_height; }, + autoscale: function (containerWidth, containerHeight, downscaleOnly) { + var targetAspectRatio = containerWidth / containerHeight; + var fbAspectRatio = this._fb_width / this._fb_height; + + var scaleRatio; + if (fbAspectRatio >= targetAspectRatio) { + scaleRatio = containerWidth / this._fb_width; + } else { + scaleRatio = containerHeight / this._fb_height; + } + + var targetW, targetH; + if (scaleRatio > 1.0 && downscaleOnly) { + targetW = this._fb_width; + targetH = this._fb_height; + scaleRatio = 1.0; + } else if (fbAspectRatio >= targetAspectRatio) { + targetW = containerWidth; + targetH = Math.round(containerWidth / fbAspectRatio); + } else { + targetW = Math.round(containerHeight * fbAspectRatio); + targetH = containerHeight; + } + + // NB(directxman12): If you set the width directly, or set the + // style width to a number, the canvas is cleared. + // However, if you set the style width to a string + // ('NNNpx'), the canvas is scaled without clearing. + this._target.style.width = targetW + 'px'; + this._target.style.height = targetH + 'px'; + + this._scale = scaleRatio; + + return scaleRatio; // so that the mouse, etc scale can be set + }, + // Private Methods _rescale: function (factor) { - var canvas = this._target; - var properties = ['transform', 'WebkitTransform', 'MozTransform']; - var transform_prop; - while ((transform_prop = properties.shift())) { - if (typeof canvas.style[transform_prop] !== 'undefined') { - break; - } - } - - if (transform_prop === null) { - Util.Debug("No scaling support"); - return; - } - - if (typeof(factor) === "undefined") { - factor = this._scale; - } else if (factor > 1.0) { - factor = 1.0; - } else if (factor < 0.1) { - factor = 0.1; - } - - if (this._scale === factor) { - return; - } - this._scale = factor; - var x = canvas.width - (canvas.width * factor); - var y = canvas.height - (canvas.height * factor); - canvas.style[transform_prop] = 'scale(' + this._scale + ') translate(-' + x + 'px, -' + y + 'px)'; + + this._target.style.width = Math.round(factor * this._fb_width) + 'px'; + this._target.style.height = Math.round(factor * this._fb_height) + 'px'; }, _setFillColor: function (color) { diff --git a/include/ui.js b/include/ui.js index 57e630bd..a5433dce 100644 --- a/include/ui.js +++ b/include/ui.js @@ -46,15 +46,29 @@ var UI; }, onresize: function (callback) { - if (UI.getSetting('resize') === 'remote') { - var innerW = window.innerWidth; - var innerH = window.innerHeight; - var controlbarH = $D('noVNC-control-bar').offsetHeight; - // For some unknown reason the container is higher than the canvas, - // 5px higher in Firefox and 4px higher in Chrome - var padding = 5; - if (innerW !== undefined && innerH !== undefined) - UI.rfb.setDesktopSize(innerW, innerH - controlbarH - padding); + var innerW = window.innerWidth; + var innerH = window.innerHeight; + var controlbarH = $D('noVNC-control-bar').offsetHeight; + // For some unknown reason the container is higher than the canvas, + // 5px higher in Firefox and 4px higher in Chrome + var padding = 5; + var effectiveH = innerH - controlbarH - padding; + + var display = UI.rfb.get_display(); + + if (innerW !== undefined && innerH !== undefined) { + var scaleType = UI.getSetting('resize'); + if (scaleType === 'remote') { + // use remote resizing + Util.Debug('Attempting setDesktopSize(' + innerW + ', ' + effectiveH + ')'); + UI.rfb.setDesktopSize(innerW, effectiveH); + } else if (scaleType === 'scale' || scaleType === 'downscale') { + // use local scaling + var downscaleOnly = scaleType === 'downscale'; + var scaleRatio = display.autoscale(innerW, effectiveH, downscaleOnly); + UI.rfb.get_mouse().set_scale(scaleRatio); + Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale()); + } } }, @@ -237,6 +251,11 @@ var UI; $D("noVNC_apply").onclick = UI.settingsApply; $D("noVNC_connect_button").onclick = UI.connect; + + $D("noVNC_resize").onchange = function () { + var connected = UI.rfb_state === 'normal' ? true : false; + UI.enableDisableClip(connected); + }; }, // Read form control compatible setting from cookie @@ -510,8 +529,14 @@ var UI; if (UI.rfb.get_display().get_cursor_uri()) { UI.saveSetting('cursor'); } - UI.saveSetting('clip'); + UI.saveSetting('resize'); + + if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') { + UI.forceSetting('clip', false); + } + + UI.saveSetting('clip'); UI.saveSetting('shared'); UI.saveSetting('view_only'); UI.saveSetting('path'); @@ -635,7 +660,8 @@ var UI; UI.updateSetting('cursor', !UI.isTouchDevice); $D('noVNC_cursor').disabled = true; } - $D('noVNC_clip').disabled = connected || UI.isTouchDevice; + + UI.enableDisableClip(connected); $D('noVNC_resize').disabled = connected; $D('noVNC_shared').disabled = connected; $D('noVNC_view_only').disabled = connected; @@ -697,6 +723,19 @@ var UI; } }, + enableDisableClip: function (connected) { + var resizeElem = $D('noVNC_resize'); + if (resizeElem.value === 'downscale' || resizeElem.value === 'scale') { + UI.forceSetting('clip', false); + $D('noVNC_clip').disabled = true; + } else { + $D('noVNC_clip').disabled = connected || UI.isTouchDevice; + if (UI.isTouchDevice) { + UI.forceSetting('clip', true); + } + } + }, + // This resize can not be done until we know from the first Frame Buffer Update // if it is supported or not. // The resize is needed to make sure the server desktop size is updated to the diff --git a/include/util.js b/include/util.js index effb0705..02e72256 100644 --- a/include/util.js +++ b/include/util.js @@ -435,8 +435,12 @@ Util.load_scripts = function (files) { Util.getPosition = function(obj) { "use strict"; + // NB(sross): the Mozilla developer reference seems to indicate that + // getBoundingClientRect includes border and padding, so the canvas + // style should NOT include either. var objPosition = obj.getBoundingClientRect(); - return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset}; + return {'x': objPosition.left + window.pageXOffset, 'y': objPosition.top + window.pageYOffset, + 'width': objPosition.width, 'height': objPosition.height}; }; @@ -462,8 +466,8 @@ Util.getEventPosition = function (e, obj, scale) { } var realx = docX - pos.x; var realy = docY - pos.y; - var x = Math.max(Math.min(realx, obj.width - 1), 0); - var y = Math.max(Math.min(realy, obj.height - 1), 0); + var x = Math.max(Math.min(realx, pos.width - 1), 0); + var y = Math.max(Math.min(realy, pos.height - 1), 0); return {'x': x / scale, 'y': y / scale, 'realx': realx / scale, 'realy': realy / scale}; }; diff --git a/tests/test.display.js b/tests/test.display.js index 949aca1e..f122dca9 100644 --- a/tests/test.display.js +++ b/tests/test.display.js @@ -154,6 +154,84 @@ describe('Display/Canvas Helper', function () { }); }); + describe('rescaling', function () { + var display; + var canvas; + + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display.resize(4, 3); + canvas = display.get_target(); + document.body.appendChild(canvas); + }); + + afterEach(function () { + document.body.removeChild(canvas); + }); + + it('should not change the bitmap size of the canvas', function () { + display.set_scale(0.5); + expect(canvas.width).to.equal(4); + expect(canvas.height).to.equal(3); + }); + + it('should change the effective rendered size of the canvas', function () { + display.set_scale(0.5); + expect(canvas.clientWidth).to.equal(2); + expect(canvas.clientHeight).to.equal(2); + }); + }); + + describe('autoscaling', function () { + var display; + var canvas; + + beforeEach(function () { + display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true }); + display.resize(4, 3); + canvas = display.get_target(); + document.body.appendChild(canvas); + }); + + afterEach(function () { + document.body.removeChild(canvas); + }); + + it('should preserve aspect ratio while autoscaling', function () { + display.autoscale(16, 9); + expect(canvas.clientWidth / canvas.clientHeight).to.equal(4 / 3); + }); + + it('should use width to determine scale when the current aspect ratio is wider than the target', function () { + expect(display.autoscale(9, 16)).to.equal(9 / 4); + expect(canvas.clientWidth).to.equal(9); + expect(canvas.clientHeight).to.equal(7); // round 9 / (4 / 3) + }); + + it('should use height to determine scale when the current aspect ratio is taller than the target', function () { + expect(display.autoscale(16, 9)).to.equal(3); // 9 / 3 + expect(canvas.clientWidth).to.equal(12); // 16 * (4 / 3) + expect(canvas.clientHeight).to.equal(9); + + }); + + it('should not change the bitmap size of the canvas', function () { + display.autoscale(16, 9); + expect(canvas.width).to.equal(4); + expect(canvas.height).to.equal(3); + }); + + it('should not upscale when downscaleOnly is true', function () { + expect(display.autoscale(2, 2, true)).to.equal(0.5); + expect(canvas.clientWidth).to.equal(2); + expect(canvas.clientHeight).to.equal(2); + + expect(display.autoscale(16, 9, true)).to.equal(1.0); + expect(canvas.clientWidth).to.equal(4); + expect(canvas.clientHeight).to.equal(3); + }); + }); + describe('drawing', function () { // TODO(directxman12): improve the tests for each of the drawing functions to cover more than just the diff --git a/vnc.html b/vnc.html index 1d1abaaf..faa4e33b 100644 --- a/vnc.html +++ b/vnc.html @@ -164,6 +164,8 @@