Commit cefc9a91 authored by Solly's avatar Solly

Merge pull request #464 from kanaka/bug/firefoxresize

Fix resize in Firefox on Android
parents 3b8ec46f fdedbafb
...@@ -112,13 +112,7 @@ html { ...@@ -112,13 +112,7 @@ html {
/* Do not set width/height for VNC_screen or VNC_canvas or incorrect /* Do not set width/height for VNC_screen or VNC_canvas or incorrect
* scaling will occur. Canvas resizes to remote VNC settings */ * scaling will occur. Canvas resizes to remote VNC settings */
#noVNC_screen_pad {
margin: 0px;
padding: 0px;
height: 36px;
}
#noVNC_screen { #noVNC_screen {
text-align: center;
display: table; display: table;
width:100%; width:100%;
height:100%; height:100%;
...@@ -127,13 +121,25 @@ html { ...@@ -127,13 +121,25 @@ html {
/*border-top-left-radius: 800px 600px;*/ /*border-top-left-radius: 800px 600px;*/
} }
#noVNC_container, #noVNC_canvas { #noVNC_container {
display: none;
position: absolute;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
bottom: 0px;
top: 36px; /* the height of the control bar */
left: 0px;
right: 0px;
width: auto;
height: auto;
} }
#noVNC_canvas { #noVNC_canvas {
left: 0px; position: absolute;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
} }
#VNC_clipboard_clear_button { #VNC_clipboard_clear_button {
......
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin * Copyright (C) 2012 Joel Martin
* Copyright (C) 2015 Samuel Mannehed for Cendio AB
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
...@@ -24,6 +25,10 @@ var Display; ...@@ -24,6 +25,10 @@ var Display;
this._fb_width = 0; this._fb_width = 0;
this._fb_height = 0; this._fb_height = 0;
// the size limit of the viewport (start disabled)
this._maxWidth = 0;
this._maxHeight = 0;
// the visible "physical canvas" viewport // the visible "physical canvas" viewport
this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 }; this._viewportLoc = { 'x': 0, 'y': 0, 'w': 0, 'h': 0 };
this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 }; this._cleanRect = { 'x1': 0, 'y1': 0, 'x2': -1, 'y2': -1 };
...@@ -202,8 +207,7 @@ var Display; ...@@ -202,8 +207,7 @@ var Display;
viewportChangeSize: function(width, height) { viewportChangeSize: function(width, height) {
if (!this._viewport || if (typeof(width) === "undefined" || typeof(height) === "undefined") {
typeof(width) === "undefined" || typeof(height) === "undefined") {
Util.Debug("Setting viewport to full display region"); Util.Debug("Setting viewport to full display region");
width = this._fb_width; width = this._fb_width;
...@@ -213,43 +217,51 @@ var Display; ...@@ -213,43 +217,51 @@ var Display;
var vp = this._viewportLoc; var vp = this._viewportLoc;
if (vp.w !== width || vp.h !== height) { if (vp.w !== width || vp.h !== height) {
if (this._viewport) {
if (this._maxWidth !== 0 && width > this._maxWidth) {
width = this._maxWidth;
}
if (this._maxHeight !== 0 && height > this._maxHeight) {
height = this._maxHeight;
}
}
var cr = this._cleanRect; var cr = this._cleanRect;
if (width < vp.w && cr.x2 > vp.x + width - 1) { if (width < vp.w && cr.x2 > vp.x + width - 1) {
cr.x2 = vp.x + width - 1; cr.x2 = vp.x + width - 1;
} }
if (height < vp.h && cr.y2 > vp.y + height - 1) { if (height < vp.h && cr.y2 > vp.y + height - 1) {
cr.y2 = vp.y + height - 1; cr.y2 = vp.y + height - 1;
} }
if (this.fbuClip()) {
// clipping
vp.w = window.innerWidth;
var cb = document.getElementById('noVNC-control-bar');
var controlbar_h = (cb !== null) ? cb.offsetHeight : 0;
vp.h = window.innerHeight - controlbar_h - 5;
} else {
// scrollbars
vp.w = width; vp.w = width;
vp.h = height; vp.h = height;
}
var saveImg = null;
var canvas = this._target; var canvas = this._target;
if (canvas.width !== width || canvas.height !== height) {
// We have to save the canvas data since changing the size will clear it
var saveImg = null;
if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) { if (vp.w > 0 && vp.h > 0 && canvas.width > 0 && canvas.height > 0) {
var img_width = canvas.width < vp.w ? canvas.width : vp.w; var img_width = canvas.width < vp.w ? canvas.width : vp.w;
var img_height = canvas.height < vp.h ? canvas.height : vp.h; var img_height = canvas.height < vp.h ? canvas.height : vp.h;
saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height); saveImg = this._drawCtx.getImageData(0, 0, img_width, img_height);
} }
canvas.width = vp.w; if (canvas.width !== width) { canvas.width = width; }
canvas.height = vp.h; if (canvas.height !== height) { canvas.height = height; }
if (this._viewport) {
canvas.style.height = height + 'px';
canvas.style.width = width + 'px';
}
if (saveImg) { if (saveImg) {
this._drawCtx.putImageData(saveImg, 0, 0); this._drawCtx.putImageData(saveImg, 0, 0);
} }
} }
}
}, },
// Return a map of clean and dirty areas of the viewport and reset the // Return a map of clean and dirty areas of the viewport and reset the
...@@ -487,12 +499,18 @@ var Display; ...@@ -487,12 +499,18 @@ var Display;
this._target.style.cursor = "none"; this._target.style.cursor = "none";
}, },
fbuClip: function () { clippingDisplay: function () {
var cb = document.getElementById('noVNC-control-bar'); var vp = this._viewportLoc;
var controlbar_h = (cb !== null) ? cb.offsetHeight : 0;
return (this._viewport && var fbClip = this._fb_width > vp.w || this._fb_height > vp.h;
(this._fb_width > window.innerWidth var limitedVp = this._maxWidth !== 0 && this._maxHeight !== 0;
|| this._fb_height > window.innerHeight - controlbar_h - 5)); var clipping = false;
if (limitedVp) {
clipping = vp.w > this._maxWidth || vp.h > this._maxHeight;
}
return fbClip || (limitedVp && clipping);
}, },
// Overridden getters/setters // Overridden getters/setters
...@@ -558,8 +576,20 @@ var Display; ...@@ -558,8 +576,20 @@ var Display;
_rescale: function (factor) { _rescale: function (factor) {
this._scale = factor; this._scale = factor;
this._target.style.width = Math.round(factor * this._fb_width) + 'px'; var w;
this._target.style.height = Math.round(factor * this._fb_height) + 'px'; var h;
if (this._viewport &&
this._maxWidth !== 0 && this._maxHeight !== 0) {
w = Math.min(this._fb_width, this._maxWidth);
h = Math.min(this._fb_height, this._maxHeight);
} else {
w = this._fb_width;
h = this._fb_height;
}
this._target.style.width = Math.round(factor * w) + 'px';
this._target.style.height = Math.round(factor * h) + 'px';
}, },
_setFillColor: function (color) { _setFillColor: function (color) {
...@@ -661,9 +691,11 @@ var Display; ...@@ -661,9 +691,11 @@ var Display;
['true_color', 'rw', 'bool'], // Use true-color pixel data ['true_color', 'rw', 'bool'], // Use true-color pixel data
['colourMap', 'rw', 'arr'], // Colour map array (when not true-color) ['colourMap', 'rw', 'arr'], // Colour map array (when not true-color)
['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0 ['scale', 'rw', 'float'], // Display area scale factor 0.0 - 1.0
['viewport', 'rw', 'bool'], // Use a viewport set with viewportChange() ['viewport', 'rw', 'bool'], // Use viewport clipping
['width', 'rw', 'int'], // Display area width ['width', 'rw', 'int'], // Display area width
['height', 'rw', 'int'], // Display area height ['height', 'rw', 'int'], // Display area height
['maxWidth', 'rw', 'int'], // Viewport max width (0 if disabled)
['maxHeight', 'rw', 'int'], // Viewport max height (0 if disabled)
['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only) ['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
......
...@@ -54,7 +54,7 @@ var RFB; ...@@ -54,7 +54,7 @@ var RFB;
['compress_hi', -247 ], ['compress_hi', -247 ],
['last_rect', -224 ], ['last_rect', -224 ],
['xvp', -309 ], ['xvp', -309 ],
['ext_desktop_size', -308 ] ['ExtendedDesktopSize', -308 ]
]; ];
this._encHandlers = {}; this._encHandlers = {};
...@@ -1871,15 +1871,27 @@ var RFB; ...@@ -1871,15 +1871,27 @@ var RFB;
return true; return true;
}, },
ext_desktop_size: function () { handle_FB_resize: function () {
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime();
this._FBU.bytes = 0;
this._FBU.rects -= 1;
return true;
},
ExtendedDesktopSize: function () {
this._FBU.bytes = 1; this._FBU.bytes = 1;
if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; } if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._supportsSetDesktopSize = true; this._supportsSetDesktopSize = true;
var number_of_screens = this._sock.rQpeek8(); var number_of_screens = this._sock.rQpeek8();
this._FBU.bytes = 4 + (number_of_screens * 16); this._FBU.bytes = 4 + (number_of_screens * 16);
if (this._sock.rQwait("ext_desktop_size", this._FBU.bytes)) { return false; } if (this._sock.rQwait("ExtendedDesktopSize", this._FBU.bytes)) { return false; }
this._sock.rQskipBytes(1); // number-of-screens this._sock.rQskipBytes(1); // number-of-screens
this._sock.rQskipBytes(3); // padding this._sock.rQskipBytes(3); // padding
...@@ -1898,31 +1910,42 @@ var RFB; ...@@ -1898,31 +1910,42 @@ var RFB;
} }
} }
if (this._FBU.x == 0 && this._FBU.y != 0) { return true; } /*
* The x-position indicates the reason for the change:
this._fb_width = this._FBU.width; *
this._fb_height = this._FBU.height; * 0 - server resized on its own
this._display.resize(this._fb_width, this._fb_height); * 1 - this client requested the resize
this._onFBResize(this, this._fb_width, this._fb_height); * 2 - another client requested the resize
*/
this._FBU.bytes = 0; // We need to handle errors when we requested the resize.
this._FBU.rects -= 1; if (this._FBU.x == 1 && this._FBU.y != 0) {
var msg = "";
// The y-position indicates the status code from the server
switch (this._FBU.y) {
case 1:
msg = "Resize is administratively prohibited";
break;
case 2:
msg = "Out of resources";
break;
case 3:
msg = "Invalid screen layout";
break;
default:
msg = "Unknown reason";
break;
}
Util.Info("Server did not accept the resize request: " + msg);
return true;
}
this._encHandlers.handle_FB_resize();
return true; return true;
}, },
DesktopSize: function () { DesktopSize: function () {
Util.Debug(">> set_desktopsize"); this._encHandlers.handle_FB_resize();
this._fb_width = this._FBU.width;
this._fb_height = this._FBU.height;
this._display.resize(this._fb_width, this._fb_height);
this._onFBResize(this, this._fb_width, this._fb_height);
this._timing.fbu_rt_start = (new Date()).getTime();
this._FBU.bytes = 0;
this._FBU.rects--;
Util.Debug("<< set_desktopsize");
return true; return true;
}, },
......
/* /*
* noVNC: HTML5 VNC client * noVNC: HTML5 VNC client
* Copyright (C) 2012 Joel Martin * Copyright (C) 2012 Joel Martin
* Copyright (C) 2013 Samuel Mannehed for Cendio AB * Copyright (C) 2015 Samuel Mannehed for Cendio AB
* Licensed under MPL 2.0 (see LICENSE.txt) * Licensed under MPL 2.0 (see LICENSE.txt)
* *
* See README.md for usage and integration instructions. * See README.md for usage and integration instructions.
...@@ -45,33 +45,6 @@ var UI; ...@@ -45,33 +45,6 @@ var UI;
WebUtil.initSettings(UI.start, callback); WebUtil.initSettings(UI.start, callback);
}, },
onresize: function (callback) {
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());
}
}
},
// Render default UI and initialize settings menu // Render default UI and initialize settings menu
start: function(callback) { start: function(callback) {
UI.isTouchDevice = 'ontouchstart' in document.documentElement; UI.isTouchDevice = 'ontouchstart' in document.documentElement;
...@@ -136,6 +109,8 @@ var UI; ...@@ -136,6 +109,8 @@ var UI;
UI.updateVisualState(); UI.updateVisualState();
$D('noVNC_host').focus();
// Show mouse selector buttons on touch screen devices // Show mouse selector buttons on touch screen devices
if (UI.isTouchDevice) { if (UI.isTouchDevice) {
// Show mobile buttons // Show mobile buttons
...@@ -148,29 +123,14 @@ var UI; ...@@ -148,29 +123,14 @@ var UI;
UI.initSetting('clip', false); UI.initSetting('clip', false);
} }
//iOS Safari does not support CSS position:fixed.
//This detects iOS devices and enables javascript workaround.
if ((navigator.userAgent.match(/iPhone/i)) ||
(navigator.userAgent.match(/iPod/i)) ||
(navigator.userAgent.match(/iPad/i))) {
//UI.setOnscroll();
//UI.setResize();
}
UI.setBarPosition();
$D('noVNC_host').focus();
UI.setViewClip(); UI.setViewClip();
UI.setBarPosition();
Util.addEvent(window, 'resize', function () { Util.addEvent(window, 'resize', function () {
UI.setViewClip();
// 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(){
UI.onresize(); UI.onresize();
}, 500); UI.setViewClip();
UI.updateViewDragButton();
UI.setBarPosition();
} ); } );
Util.addEvent(window, 'load', UI.keyboardinputReset); Util.addEvent(window, 'load', UI.keyboardinputReset);
...@@ -258,6 +218,55 @@ var UI; ...@@ -258,6 +218,55 @@ var UI;
}; };
}, },
onresize: function (callback) {
var size = UI.getCanvasLimit();
if (size && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
var display = UI.rfb.get_display();
var scaleType = UI.getSetting('resize');
if (scaleType === 'remote') {
// use remote resizing
// When the local 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(){
display.set_maxWidth(size.w);
display.set_maxHeight(size.h);
Util.Debug('Attempting setDesktopSize(' +
size.w + ', ' + size.h + ')');
UI.rfb.setDesktopSize(size.w, size.h);
}, 500);
} else if (scaleType === 'scale' || scaleType === 'downscale') {
// use local scaling
var downscaleOnly = scaleType === 'downscale';
var scaleRatio = display.autoscale(size.w, size.h, downscaleOnly);
UI.rfb.get_mouse().set_scale(scaleRatio);
Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
}
}
},
getCanvasLimit: function () {
var container = $D('noVNC_container');
// Hide the scrollbars until the size is calculated
container.style.overflow = "hidden";
var w = Util.getPosition(container).width;
var h = Util.getPosition(container).height;
container.style.overflow = "visible";
if (isNaN(w) || isNaN(h)) {
return false;
} else {
return {w: w, h: h};
}
},
// Read form control compatible setting from cookie // Read form control compatible setting from cookie
getSetting: function(name) { getSetting: function(name) {
var ctrl = $D('noVNC_' + name); var ctrl = $D('noVNC_' + name);
...@@ -613,6 +622,7 @@ var UI; ...@@ -613,6 +622,7 @@ var UI;
break; break;
case 'disconnected': case 'disconnected':
$D('noVNC_logo').style.display = "block"; $D('noVNC_logo').style.display = "block";
$D('noVNC_container').style.display = "none";
/* falls through */ /* falls through */
case 'loaded': case 'loaded':
klass = "noVNC_status_normal"; klass = "noVNC_status_normal";
...@@ -781,6 +791,7 @@ var UI; ...@@ -781,6 +791,7 @@ var UI;
//Close dialog. //Close dialog.
setTimeout(UI.setBarPosition, 100); setTimeout(UI.setBarPosition, 100);
$D('noVNC_logo').style.display = "none"; $D('noVNC_logo').style.display = "none";
$D('noVNC_container').style.display = "inline";
}, },
disconnect: function() { disconnect: function() {
...@@ -791,6 +802,8 @@ var UI; ...@@ -791,6 +802,8 @@ var UI;
UI.rfb.set_onFBUComplete(UI.FBUComplete); UI.rfb.set_onFBUComplete(UI.FBUComplete);
$D('noVNC_logo').style.display = "block"; $D('noVNC_logo').style.display = "block";
$D('noVNC_container').style.display = "none";
// Don't display the connection settings until we're actually disconnected // Don't display the connection settings until we're actually disconnected
}, },
...@@ -839,17 +852,30 @@ var UI; ...@@ -839,17 +852,30 @@ var UI;
// Turn clipping off // Turn clipping off
UI.updateSetting('clip', false); UI.updateSetting('clip', false);
display.set_viewport(false); display.set_viewport(false);
$D('noVNC_canvas').style.position = 'static'; display.set_maxWidth(0);
display.set_maxHeight(0);
display.viewportChangeSize(); display.viewportChangeSize();
} }
if (UI.getSetting('clip')) { if (UI.getSetting('clip')) {
// If clipping, update clipping settings // If clipping, update clipping settings
$D('noVNC_canvas').style.position = 'absolute';
var pos = Util.getPosition($D('noVNC_canvas'));
var new_w = window.innerWidth - pos.x;
var new_h = window.innerHeight - pos.y;
display.set_viewport(true); display.set_viewport(true);
display.viewportChangeSize(new_w, new_h);
var size = UI.getCanvasLimit();
if (size) {
display.set_maxWidth(size.w);
display.set_maxHeight(size.h);
// Hide potential scrollbars that can skew the position
$D('noVNC_container').style.overflow = "hidden";
// The x position marks the left margin of the canvas,
// remove the margin from both sides to keep it centered
var new_w = size.w - (2 * Util.getPosition($D('noVNC_canvas')).x);
$D('noVNC_container').style.overflow = "visible";
display.viewportChangeSize(new_w, size.h);
}
} }
}, },
...@@ -878,7 +904,7 @@ var UI; ...@@ -878,7 +904,7 @@ var UI;
var vmb = $D('noVNC_view_drag_button'); var vmb = $D('noVNC_view_drag_button');
if (UI.rfb_state === 'normal' && if (UI.rfb_state === 'normal' &&
UI.rfb.get_display().get_viewport() && UI.rfb.get_display().get_viewport() &&
UI.rfb.get_display().fbuClip()) { UI.rfb.get_display().clippingDisplay()) {
vmb.style.display = "inline"; vmb.style.display = "inline";
} else { } else {
vmb.style.display = "none"; vmb.style.display = "none";
...@@ -1058,19 +1084,6 @@ var UI; ...@@ -1058,19 +1084,6 @@ var UI;
UI.keyboardVisible = false; UI.keyboardVisible = false;
}, },
// iOS < Version 5 does not support position fixed. Javascript workaround:
setOnscroll: function() {
window.onscroll = function() {
UI.setBarPosition();
};
},
setResize: function () {
window.onResize = function() {
UI.setBarPosition();
};
},
//Helper to add options to dropdown. //Helper to add options to dropdown.
addOption: function(selectbox, text, value) { addOption: function(selectbox, text, value) {
var optn = document.createElement("OPTION"); var optn = document.createElement("OPTION");
......
...@@ -134,6 +134,40 @@ describe('Display/Canvas Helper', function () { ...@@ -134,6 +134,40 @@ describe('Display/Canvas Helper', function () {
}); });
}); });
describe('clipping', function () {
var display;
beforeEach(function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false, viewport: true });
display.resize(4, 3);
});
it('should report true when no max-size and framebuffer > viewport', function () {
display.viewportChangeSize(2,2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
it('should report false when no max-size and framebuffer = viewport', function () {
var clipping = display.clippingDisplay();
expect(clipping).to.be.false;
});
it('should report true when viewport > max-size and framebuffer > viewport', function () {
display.viewportChangeSize(2,2);
display.set_maxWidth(1);
display.set_maxHeight(2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
it('should report true when viewport > max-size and framebuffer = viewport', function () {
display.set_maxWidth(1);
display.set_maxHeight(2);
var clipping = display.clippingDisplay();
expect(clipping).to.be.true;
});
});
describe('resizing', function () { describe('resizing', function () {
var display; var display;
beforeEach(function () { beforeEach(function () {
......
...@@ -1584,7 +1584,7 @@ describe('Remote Frame Buffer Protocol Client', function() { ...@@ -1584,7 +1584,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
}); });
it('should not handle a failed request', function () { it('should not handle a failed request', function () {
var reason_for_change = 0; // non-incremental var reason_for_change = 1; // requested by this client
var status_code = 1; // Resize is administratively prohibited var status_code = 1; // Resize is administratively prohibited
send_fbu_msg([{ x: reason_for_change, y: status_code, send_fbu_msg([{ x: reason_for_change, y: status_code,
......
...@@ -203,13 +203,11 @@ ...@@ -203,13 +203,11 @@
<div id="noVNC_screen"> <div id="noVNC_screen">
<div id="noVNC_screen_pad"></div>
<h1 id="noVNC_logo"><span>no</span><br />VNC</h1> <h1 id="noVNC_logo"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas --> <!-- HTML5 Canvas -->
<div id="noVNC_container"> <div id="noVNC_container">
<canvas id="noVNC_canvas" width="640px" height="20px"> <canvas id="noVNC_canvas" width="0" height="0">
Canvas not supported. Canvas not supported.
</canvas> </canvas>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment