diff options
author | Samuel <samuel@cendio.se> | 2015-03-05 09:52:10 +0100 |
---|---|---|
committer | Samuel <samuel@cendio.se> | 2015-03-05 09:52:10 +0100 |
commit | 205d1a11ce71744f1e95c6849f9652b65487a652 (patch) | |
tree | 3963841f5bce9f85199e8fc2ed49050f60320d6a | |
parent | 7e161007abe8338afc501e907993bf5a72e085d9 (diff) | |
parent | 72747869a7a95b72d26914508ce8ec5670eecc5b (diff) | |
download | novnc-205d1a11ce71744f1e95c6849f9652b65487a652.tar.gz |
Merge pull request #451 from kanaka/feature/scaling
Introduce Local Autoscaling
-rw-r--r-- | include/display.js | 60 | ||||
-rw-r--r-- | include/ui.js | 63 | ||||
-rw-r--r-- | include/util.js | 10 | ||||
-rw-r--r-- | tests/test.display.js | 78 | ||||
-rw-r--r-- | vnc.html | 10 |
5 files changed, 180 insertions, 41 deletions
diff --git a/include/display.js b/include/display.js index d127868..2b1b827 100644 --- a/include/display.js +++ b/include/display.js @@ -518,38 +518,48 @@ var Display; return this._fb_height; }, - // 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; - } - } + autoscale: function (containerWidth, containerHeight, downscaleOnly) { + var targetAspectRatio = containerWidth / containerHeight; + var fbAspectRatio = this._fb_width / this._fb_height; - if (transform_prop === null) { - Util.Debug("No scaling support"); - return; + var scaleRatio; + if (fbAspectRatio >= targetAspectRatio) { + scaleRatio = containerWidth / this._fb_width; + } else { + scaleRatio = containerHeight / this._fb_height; } - if (typeof(factor) === "undefined") { - factor = this._scale; - } else if (factor > 1.0) { - factor = 1.0; - } else if (factor < 0.1) { - factor = 0.1; + 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; } - if (this._scale === factor) { - return; - } + // 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) { 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 e923ea8..a5433dc 100644 --- a/include/ui.js +++ b/include/ui.js @@ -46,15 +46,29 @@ var UI; }, onresize: function (callback) { - if (UI.getSetting('resize')) { - 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()); + } } }, @@ -104,7 +118,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'); @@ -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 effb070..02e7225 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 949aca1..f122dca 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 @@ -157,10 +157,18 @@ <li><input id="noVNC_true_color" type="checkbox" checked> True Color</li> <li><input id="noVNC_cursor" type="checkbox"> Local Cursor</li> <li><input id="noVNC_clip" type="checkbox"> Clip to Window</li> - <li><input id="noVNC_resize" type="checkbox"> Resize Remote to Window</li> <li><input id="noVNC_shared" type="checkbox"> Shared Mode</li> <li><input id="noVNC_view_only" type="checkbox"> View Only</li> + <hr> <li><input id="noVNC_path" type="input" value="websockify"> Path</li> + <li><label> + <select id="noVNC_resize" name="vncResize"> + <option value="off">None</option> + <option value="scale">Local Scaling</option> + <option value="downscale">Local Downscaling</option> + <option value="remote">Remote Resizing</option> + </select> Scaling Mode</label> + </li> <li><input id="noVNC_repeaterID" type="input" value=""> Repeater ID</li> <hr> <!-- Stylesheet selection dropdown --> |