OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Class handling creation and teardown of a remoting client session. | 7 * Class handling creation and teardown of a remoting client session. |
8 * | 8 * |
9 * The ClientSession class controls lifetime of the client plugin | 9 * The ClientSession class controls lifetime of the client plugin |
10 * object and provides the plugin with the functionality it needs to | 10 * object and provides the plugin with the functionality it needs to |
(...skipping 1219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1230 * remote desktop and client window, and the current scale-to-fit setting. | 1230 * remote desktop and client window, and the current scale-to-fit setting. |
1231 * | 1231 * |
1232 * @return {void} Nothing. | 1232 * @return {void} Nothing. |
1233 */ | 1233 */ |
1234 remoting.ClientSession.prototype.updateDimensions = function() { | 1234 remoting.ClientSession.prototype.updateDimensions = function() { |
1235 if (this.plugin_.getDesktopWidth() == 0 || | 1235 if (this.plugin_.getDesktopWidth() == 0 || |
1236 this.plugin_.getDesktopHeight() == 0) { | 1236 this.plugin_.getDesktopHeight() == 0) { |
1237 return; | 1237 return; |
1238 } | 1238 } |
1239 | 1239 |
1240 var clientArea = this.getClientArea_(); | 1240 var desktopSize = { width: this.plugin_.getDesktopWidth(), |
1241 var desktopWidth = this.plugin_.getDesktopWidth(); | 1241 height: this.plugin_.getDesktopHeight() }; |
1242 var desktopHeight = this.plugin_.getDesktopHeight(); | 1242 var desktopDpi = { x: this.plugin_.getDesktopXDpi(), |
| 1243 y: this.plugin_.getDesktopYDpi() }; |
| 1244 var newSize = remoting.ClientSession.choosePluginSize( |
| 1245 this.getClientArea_(), window.devicePixelRatio, |
| 1246 desktopSize, desktopDpi, this.desktopScale_, |
| 1247 remoting.fullscreen.isActive(), this.shrinkToFit_); |
1243 | 1248 |
1244 // When configured to display a host at its original size, we aim to display | 1249 // Resize the plugin if necessary. |
1245 // it as close to its physical size as possible, without losing data: | 1250 console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height); |
1246 // - If client and host have matching DPI, render the host pixel-for-pixel. | 1251 this.plugin_.element().style.width = newSize.width + 'px'; |
1247 // - If the host has higher DPI then still render pixel-for-pixel. | 1252 this.plugin_.element().style.height = newSize.height + 'px'; |
1248 // - If the host has lower DPI then let Chrome up-scale it to natural size. | |
1249 | 1253 |
1250 // We specify the plugin dimensions in Density-Independent Pixels, so to | 1254 // When we receive the first plugin dimensions from the host, we know that |
1251 // render pixel-for-pixel we need to down-scale the host dimensions by the | 1255 // remote host has started. |
1252 // devicePixelRatio of the client. To match the host pixel density, we choose | 1256 remoting.app.onVideoStreamingStarted(); |
1253 // an initial scale factor based on the client devicePixelRatio and host DPI. | 1257 } |
1254 | 1258 |
1255 // Determine the effective device pixel ratio of the host, based on DPI. | 1259 /** |
1256 var hostPixelRatioX = Math.ceil(this.plugin_.getDesktopXDpi() / 96); | 1260 * Helper function accepting client and host dimensions, and returning a chosen |
1257 var hostPixelRatioY = Math.ceil(this.plugin_.getDesktopYDpi() / 96); | 1261 * size for the plugin element, in DIPs. |
| 1262 * |
| 1263 * @param {{width: number, height: number}} clientSizeDips Available client |
| 1264 * dimensions, in DIPs. |
| 1265 * @param {number} clientPixelRatio Number of physical pixels per client DIP. |
| 1266 * @param {{width: number, height: number}} desktopSize Size of the host desktop |
| 1267 * in physical pixels. |
| 1268 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both |
| 1269 * dimensions. |
| 1270 * @param {number} desktopScale The scale factor configured for the host. |
| 1271 * @param {boolean} isFullscreen True if full-screen mode is active. |
| 1272 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied. |
| 1273 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs. |
| 1274 */ |
| 1275 remoting.ClientSession.choosePluginSize = function( |
| 1276 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale, |
| 1277 isFullscreen, shrinkToFit) { |
| 1278 base.debug.assert(clientSizeDips.width > 0); |
| 1279 base.debug.assert(clientSizeDips.height > 0); |
| 1280 base.debug.assert(clientPixelRatio >= 1.0); |
| 1281 base.debug.assert(desktopSize.width > 0); |
| 1282 base.debug.assert(desktopSize.height > 0); |
| 1283 base.debug.assert(desktopDpi.x > 0); |
| 1284 base.debug.assert(desktopDpi.y > 0); |
| 1285 base.debug.assert(desktopScale > 0); |
| 1286 |
| 1287 // We have the following goals in sizing the desktop display at the client: |
| 1288 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels. |
| 1289 // 2. Avoid up-scaling if that will cause the client to need scrollbars. |
| 1290 // 3. Avoid introducing blurriness with non-integer up-scaling factors. |
| 1291 // 4. Avoid having huge "letterboxes" around the desktop, if it's really |
| 1292 // small. |
| 1293 // 5. Compensate for mismatched DPIs, so that the behaviour of features like |
| 1294 // shrink-to-fit matches their "natural" rather than their pixel size. |
| 1295 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480 |
| 1296 // high-DPI client will be up-scaled to 1280x960, rather than displayed |
| 1297 // at 1:1 host:physical client pixels. |
| 1298 // |
| 1299 // To determine the ideal size we follow a four-stage process: |
| 1300 // 1. Determine the "natural" size at which to display the desktop. |
| 1301 // a. Initially assume 1:1 mapping of desktop to client device pixels. |
| 1302 // b. If host DPI is less than the client's then up-scale accordingly. |
| 1303 // c. If desktopScale is configured for the host then allow that to |
| 1304 // reduce the amount of up-scaling from (b). e.g. if the client:host |
| 1305 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale |
| 1306 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling. |
| 1307 // 2. If the natural size of the desktop is smaller than the client device |
| 1308 // then apply up-scaling by an integer scale factor to avoid excessive |
| 1309 // letterboxing. |
| 1310 // 3. If shrink-to-fit is configured then: |
| 1311 // a. If the natural size exceeds the client size then apply down-scaling |
| 1312 // by an arbitrary scale factor. |
| 1313 // b. If we're in full-screen mode and the client & host aspect-ratios |
| 1314 // are radically different (e.g. the host is actually multi-monitor) |
| 1315 // then shrink-to-fit to the shorter dimension, rather than leaving |
| 1316 // huge letterboxes; the user can then bump-scroll around the desktop. |
| 1317 // 4. If the overall scale factor is fractionally over an integer factor |
| 1318 // then reduce it to that integer factor, to avoid blurring. |
| 1319 |
| 1320 // All calculations are performed in device pixels. |
| 1321 var clientWidth = clientSizeDips.width * clientPixelRatio; |
| 1322 var clientHeight = clientSizeDips.height * clientPixelRatio; |
| 1323 |
| 1324 // 1. Determine a "natural" size at which to display the desktop. |
| 1325 var scale = 1.0; |
| 1326 |
| 1327 // Determine the effective host device pixel ratio. |
| 1328 // Note that we round up or down to the closest integer pixel ratio. |
| 1329 var hostPixelRatioX = Math.round(desktopDpi.x / 96); |
| 1330 var hostPixelRatioY = Math.round(desktopDpi.y / 96); |
1258 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); | 1331 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); |
1259 | 1332 |
1260 // Include the desktopScale in the hostPixelRatio before comparing it with | 1333 // Allow up-scaling to account for DPI. |
1261 // the client devicePixelRatio to determine the "natural" scale to use. | 1334 scale = Math.max(scale, clientPixelRatio / hostPixelRatio); |
1262 hostPixelRatio *= this.desktopScale_; | |
1263 | 1335 |
1264 // Down-scale by the smaller of the client and host ratios. | 1336 // Allow some or all of the up-scaling to be cancelled by the desktopScale. |
1265 var scale = 1.0 / Math.min(window.devicePixelRatio, hostPixelRatio); | 1337 if (desktopScale > 1.0) { |
| 1338 scale = Math.max(1.0, scale / desktopScale); |
| 1339 } |
1266 | 1340 |
1267 if (this.shrinkToFit_) { | 1341 // 2. If the host is still much smaller than the client, then up-scale to |
1268 // Reduce the scale, if necessary, to fit the whole desktop in the window. | 1342 // avoid wasting space, but only by an integer factor, to avoid blurring. |
1269 var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth); | 1343 if (desktopSize.width * scale <= clientWidth && |
1270 var scaleFitHeight = | 1344 desktopSize.height * scale <= clientHeight) { |
1271 Math.min(scale, 1.0 * clientArea.height / desktopHeight); | 1345 var scaleX = Math.floor(clientWidth / desktopSize.width); |
| 1346 var scaleY = Math.floor(clientHeight / desktopSize.height); |
| 1347 scale = Math.min(scaleX, scaleY); |
| 1348 base.debug.assert(scale >= 1.0); |
| 1349 } |
| 1350 |
| 1351 // 3. Apply shrink-to-fit, if configured. |
| 1352 if (shrinkToFit) { |
| 1353 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width); |
| 1354 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height); |
1272 scale = Math.min(scaleFitHeight, scaleFitWidth); | 1355 scale = Math.min(scaleFitHeight, scaleFitWidth); |
1273 | 1356 |
1274 // If we're running full-screen then try to handle common side-by-side | 1357 // If we're running full-screen then try to handle common side-by-side |
1275 // multi-monitor combinations more intelligently. | 1358 // multi-monitor combinations more intelligently. |
1276 if (remoting.fullscreen.isActive()) { | 1359 if (isFullscreen) { |
1277 // If the host has two monitors each the same size as the client then | 1360 // If the host has two monitors each the same size as the client then |
1278 // scale-to-fit will have the desktop occupy only 50% of the client area, | 1361 // scale-to-fit will have the desktop occupy only 50% of the client area, |
1279 // in which case it would be preferable to down-scale less and let the | 1362 // in which case it would be preferable to down-scale less and let the |
1280 // user bump-scroll around ("scale-and-pan"). | 1363 // user bump-scroll around ("scale-and-pan"). |
1281 // Triggering scale-and-pan if less than 65% of the client area would be | 1364 // Triggering scale-and-pan if less than 65% of the client area would be |
1282 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to | 1365 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to |
1283 // a (2x1280)x1024 host nicely. | 1366 // a (2x1280)x1024 host nicely. |
1284 // Note that we don't need to account for scrollbars while fullscreen. | 1367 // Note that we don't need to account for scrollbars while fullscreen. |
1285 if (scale <= scaleFitHeight * 0.65) { | 1368 if (scale <= scaleFitHeight * 0.65) { |
1286 scale = scaleFitHeight; | 1369 scale = scaleFitHeight; |
1287 } | 1370 } |
1288 if (scale <= scaleFitWidth * 0.65) { | 1371 if (scale <= scaleFitWidth * 0.65) { |
1289 scale = scaleFitWidth; | 1372 scale = scaleFitWidth; |
1290 } | 1373 } |
1291 } | 1374 } |
1292 } | 1375 } |
1293 | 1376 |
1294 var pluginWidth = Math.round(desktopWidth * scale); | 1377 // 4. Avoid blurring for close-to-integer up-scaling factors. |
1295 var pluginHeight = Math.round(desktopHeight * scale); | 1378 if (scale > 1.0) { |
| 1379 var scaleBlurriness = scale / Math.floor(scale); |
| 1380 if (scaleBlurriness < 1.1) { |
| 1381 scale = Math.floor(scale); |
| 1382 } |
| 1383 } |
1296 | 1384 |
1297 // Resize the plugin if necessary. | 1385 // Return the necessary plugin dimensions in DIPs. |
1298 // TODO(wez): Handle high-DPI to high-DPI properly (crbug.com/135089). | 1386 scale = scale / clientPixelRatio; |
1299 this.plugin_.element().style.width = pluginWidth + 'px'; | 1387 var pluginWidth = Math.round(desktopSize.width * scale); |
1300 this.plugin_.element().style.height = pluginHeight + 'px'; | 1388 var pluginHeight = Math.round(desktopSize.height * scale); |
1301 | 1389 return { width: pluginWidth, height: pluginHeight }; |
1302 // Position the container. | 1390 } |
1303 // Note that clientWidth/Height take into account scrollbars. | |
1304 var clientWidth = document.documentElement.clientWidth; | |
1305 var clientHeight = document.documentElement.clientHeight; | |
1306 var parentNode = this.plugin_.element().parentNode; | |
1307 | |
1308 console.log('plugin dimensions: ' + | |
1309 parentNode.style.left + ',' + | |
1310 parentNode.style.top + '-' + | |
1311 pluginWidth + 'x' + pluginHeight + '.'); | |
1312 | |
1313 // When we receive the first plugin dimensions from the host, we know that | |
1314 // remote host has started. | |
1315 remoting.app.onVideoStreamingStarted(); | |
1316 }; | |
1317 | 1391 |
1318 /** | 1392 /** |
1319 * Returns an associative array with a set of stats for this connection. | 1393 * Returns an associative array with a set of stats for this connection. |
1320 * | 1394 * |
1321 * @return {remoting.ClientSession.PerfStats} The connection statistics. | 1395 * @return {remoting.ClientSession.PerfStats} The connection statistics. |
1322 */ | 1396 */ |
1323 remoting.ClientSession.prototype.getPerfStats = function() { | 1397 remoting.ClientSession.prototype.getPerfStats = function() { |
1324 return this.plugin_.getPerfStats(); | 1398 return this.plugin_.getPerfStats(); |
1325 }; | 1399 }; |
1326 | 1400 |
(...skipping 383 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1710 * @param {Object} message The parsed extension message data. | 1784 * @param {Object} message The parsed extension message data. |
1711 * @return {boolean} True if the message was recognized, false otherwise. | 1785 * @return {boolean} True if the message was recognized, false otherwise. |
1712 */ | 1786 */ |
1713 remoting.ClientSession.prototype.handleExtensionMessage = | 1787 remoting.ClientSession.prototype.handleExtensionMessage = |
1714 function(type, message) { | 1788 function(type, message) { |
1715 if (this.videoFrameRecorder_) { | 1789 if (this.videoFrameRecorder_) { |
1716 return this.videoFrameRecorder_.handleMessage(type, message); | 1790 return this.videoFrameRecorder_.handleMessage(type, message); |
1717 } | 1791 } |
1718 return false; | 1792 return false; |
1719 } | 1793 } |
OLD | NEW |