OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 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 user-facing aspects of the client session. | 7 * Class handling user-facing aspects of the client session. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | 10 'use strict'; |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
60 */ | 60 */ |
61 this.onInitialized_ = onInitialized; | 61 this.onInitialized_ = onInitialized; |
62 | 62 |
63 /** @type {function(boolean=):void} @private */ | 63 /** @type {function(boolean=):void} @private */ |
64 this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this) | 64 this.callOnFullScreenChanged_ = this.onFullScreenChanged_.bind(this) |
65 | 65 |
66 /** @private */ | 66 /** @private */ |
67 this.callPluginLostFocus_ = this.pluginLostFocus_.bind(this); | 67 this.callPluginLostFocus_ = this.pluginLostFocus_.bind(this); |
68 /** @private */ | 68 /** @private */ |
69 this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this); | 69 this.callPluginGotFocus_ = this.pluginGotFocus_.bind(this); |
70 | |
71 /** @type {number?} @private */ | |
72 this.notifyClientResolutionTimer_ = null; | |
73 | |
74 /** @type {Element} @private */ | 70 /** @type {Element} @private */ |
75 this.mouseCursorOverlay_ = | 71 this.mouseCursorOverlay_ = |
76 this.container_.querySelector('.mouse-cursor-overlay'); | 72 this.container_.querySelector('.mouse-cursor-overlay'); |
77 | 73 |
74 /** @private {remoting.DesktopViewport} */ | |
75 this.viewport_ = null; | |
76 | |
78 /** @type {Element} */ | 77 /** @type {Element} */ |
79 var img = this.mouseCursorOverlay_; | 78 var img = this.mouseCursorOverlay_; |
80 /** @param {Event} event @private */ | 79 /** @param {Event} event @private */ |
81 this.updateMouseCursorPosition_ = function(event) { | 80 this.updateMouseCursorPosition_ = function(event) { |
82 img.style.top = event.y + 'px'; | 81 img.style.top = event.y + 'px'; |
83 img.style.left = event.x + 'px'; | 82 img.style.left = event.x + 'px'; |
84 }; | 83 }; |
85 | 84 |
86 /** @type {number?} @private */ | |
87 this.bumpScrollTimer_ = null; | |
88 | |
89 // Bump-scroll test variables. Override to use a fake value for the width | |
90 // and height of the client plugin so that bump-scrolling can be tested | |
91 // without relying on the actual size of the host desktop. | |
92 /** @type {number} @private */ | |
93 this.pluginWidthForBumpScrollTesting_ = 0; | |
94 /** @type {number} @private */ | |
95 this.pluginHeightForBumpScrollTesting_ = 0; | |
96 | |
97 /** @type {remoting.VideoFrameRecorder} @private */ | 85 /** @type {remoting.VideoFrameRecorder} @private */ |
98 this.videoFrameRecorder_ = null; | 86 this.videoFrameRecorder_ = null; |
99 | |
100 /** @private {base.Disposables} */ | |
101 this.eventHooks_ = null; | |
102 | |
103 this.defineEvents(base.values(remoting.DesktopConnectedView.Events)); | |
104 }; | |
105 | |
106 base.extend(remoting.DesktopConnectedView, base.EventSourceImpl); | |
107 | |
108 /** @enum {string} */ | |
109 remoting.DesktopConnectedView.Events = { | |
110 bumpScrollStarted: 'bumpScrollStarted', | |
111 bumpScrollStopped: 'bumpScrollStopped' | |
112 }; | 87 }; |
113 | 88 |
114 // The mode of this session. | 89 // The mode of this session. |
115 /** @enum {number} */ | 90 /** @enum {number} */ |
116 remoting.DesktopConnectedView.Mode = { | 91 remoting.DesktopConnectedView.Mode = { |
117 IT2ME: 0, | 92 IT2ME: 0, |
118 ME2ME: 1, | 93 ME2ME: 1, |
119 APP_REMOTING: 2 | 94 APP_REMOTING: 2 |
120 }; | 95 }; |
121 | 96 |
(...skipping 16 matching lines...) Expand all Loading... | |
138 * @return {remoting.DesktopConnectedView.Mode} The current state. | 113 * @return {remoting.DesktopConnectedView.Mode} The current state. |
139 */ | 114 */ |
140 remoting.DesktopConnectedView.prototype.getMode = function() { | 115 remoting.DesktopConnectedView.prototype.getMode = function() { |
141 return this.mode_; | 116 return this.mode_; |
142 }; | 117 }; |
143 | 118 |
144 /** | 119 /** |
145 * @return {boolean} True if shrink-to-fit is enabled; false otherwise. | 120 * @return {boolean} True if shrink-to-fit is enabled; false otherwise. |
146 */ | 121 */ |
147 remoting.DesktopConnectedView.prototype.getShrinkToFit = function() { | 122 remoting.DesktopConnectedView.prototype.getShrinkToFit = function() { |
148 return this.host_.options.shrinkToFit; | 123 if (this.viewport_) { |
124 return this.viewport_.getShrinkToFit(); | |
125 } | |
Jamie
2015/02/23 21:22:51
Add an explicit return false here (and below).
kelvinp
2015/02/23 23:42:30
Done.
| |
149 }; | 126 }; |
150 | 127 |
151 /** | 128 /** |
152 * @return {boolean} True if resize-to-client is enabled; false otherwise. | 129 * @return {boolean} True if resize-to-client is enabled; false otherwise. |
153 */ | 130 */ |
154 remoting.DesktopConnectedView.prototype.getResizeToClient = function() { | 131 remoting.DesktopConnectedView.prototype.getResizeToClient = function() { |
155 return this.host_.options.resizeToClient; | 132 if (this.viewport_) { |
133 return this.viewport_.getResizeToClient(); | |
134 } | |
156 }; | 135 }; |
157 | 136 |
158 /** | 137 /** |
159 * @return {Element} The element that should host the plugin. | 138 * @return {Element} The element that should host the plugin. |
139 * @private | |
160 */ | 140 */ |
161 remoting.DesktopConnectedView.prototype.getPluginContainer = function() { | 141 remoting.DesktopConnectedView.prototype.getPluginContainer_ = function() { |
162 return this.container_.querySelector('.client-plugin-container') | 142 return this.container_.querySelector('.client-plugin-container'); |
143 }; | |
144 | |
145 /** @return {remoting.DesktopViewport} */ | |
146 remoting.DesktopConnectedView.prototype.getViewportForTesting = function() { | |
147 return this.viewport_; | |
163 }; | 148 }; |
164 | 149 |
165 /** | 150 /** |
166 * @return {{width: number, height: number}} The height of the window's client | |
167 * area. This differs between apps v1 and apps v2 due to the custom window | |
168 * borders used by the latter. | |
169 * TODO: make private | |
170 */ | |
171 remoting.DesktopConnectedView.prototype.getClientArea_ = function() { | |
172 return remoting.windowFrame ? | |
173 remoting.windowFrame.getClientArea() : | |
174 { 'width': window.innerWidth, 'height': window.innerHeight }; | |
175 }; | |
176 | |
177 /** | |
178 * @param {number} width | |
179 * @param {number} height | |
180 */ | |
181 remoting.DesktopConnectedView.prototype.setPluginSizeForBumpScrollTesting = | |
182 function(width, height) { | |
183 this.pluginWidthForBumpScrollTesting_ = width; | |
184 this.pluginHeightForBumpScrollTesting_ = height; | |
185 } | |
186 | |
187 /** | |
188 * Notifies the host of the client's current dimensions and DPI. | |
189 * Also takes into account per-host scaling factor, if configured. | |
190 * TODO: private | |
191 */ | |
192 remoting.DesktopConnectedView.prototype.notifyClientResolution_ = function() { | |
193 var clientArea = this.getClientArea_(); | |
194 var desktopScale = this.host_.options.desktopScale; | |
195 this.plugin_.hostDesktop().resize(clientArea.width * desktopScale, | |
196 clientArea.height * desktopScale, | |
197 window.devicePixelRatio); | |
198 }; | |
199 | |
200 /** | |
201 * Adds <embed> element to the UI container and readies the session object. | 151 * Adds <embed> element to the UI container and readies the session object. |
202 * | 152 * |
203 * @param {function(string, string):boolean} onExtensionMessage The handler for | 153 * @param {function(string, string):boolean} onExtensionMessage The handler for |
204 * protocol extension messages. Returns true if a message is recognized; | 154 * protocol extension messages. Returns true if a message is recognized; |
205 * false otherwise. | 155 * false otherwise. |
206 * @param {Array<string>} requiredCapabilities A list of capabilities | 156 * @param {Array<string>} requiredCapabilities A list of capabilities |
207 * required by this application. | 157 * required by this application. |
208 */ | 158 */ |
209 remoting.DesktopConnectedView.prototype.createPluginAndConnect = | 159 remoting.DesktopConnectedView.prototype.createPluginAndConnect = |
210 function(onExtensionMessage, requiredCapabilities) { | 160 function(onExtensionMessage, requiredCapabilities) { |
211 this.plugin_ = remoting.ClientPlugin.factory.createPlugin( | 161 this.plugin_ = remoting.ClientPlugin.factory.createPlugin( |
212 this.getPluginContainer(), | 162 this.getPluginContainer_(), |
213 onExtensionMessage, requiredCapabilities); | 163 onExtensionMessage, requiredCapabilities); |
214 var that = this; | 164 var that = this; |
215 this.host_.options.load().then(function(){ | 165 this.host_.options.load().then(function(){ |
216 that.plugin_.initialize(that.onPluginInitialized_.bind(that)); | 166 that.plugin_.initialize(that.onPluginInitialized_.bind(that)); |
217 }); | 167 }); |
218 }; | 168 }; |
219 | 169 |
220 /** | 170 /** |
221 * @param {boolean} initialized | 171 * @param {boolean} initialized |
222 */ | 172 */ |
(...skipping 27 matching lines...) Expand all Loading... | |
250 this.applyRemapKeys_(true); | 200 this.applyRemapKeys_(true); |
251 } | 201 } |
252 | 202 |
253 // TODO(wez): Only allow mouse lock if the app has the pointerLock permission. | 203 // TODO(wez): Only allow mouse lock if the app has the pointerLock permission. |
254 // Enable automatic mouse-lock. | 204 // Enable automatic mouse-lock. |
255 if (remoting.enableMouseLock && | 205 if (remoting.enableMouseLock && |
256 this.plugin_.hasFeature(remoting.ClientPlugin.Feature.ALLOW_MOUSE_LOCK)) { | 206 this.plugin_.hasFeature(remoting.ClientPlugin.Feature.ALLOW_MOUSE_LOCK)) { |
257 this.plugin_.allowMouseLock(); | 207 this.plugin_.allowMouseLock(); |
258 } | 208 } |
259 | 209 |
260 base.dispose(this.eventHooks_); | |
261 var hostDesktop = this.plugin_.hostDesktop(); | |
262 this.eventHooks_ = new base.Disposables( | |
263 new base.EventHook( | |
264 hostDesktop, remoting.HostDesktop.Events.sizeChanged, | |
265 this.onDesktopSizeChanged_.bind(this)), | |
266 new base.EventHook( | |
267 hostDesktop, remoting.HostDesktop.Events.shapeChanged, | |
268 this.onDesktopShapeChanged_.bind(this))); | |
269 | |
270 this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this)); | 210 this.plugin_.setMouseCursorHandler(this.updateMouseCursorImage_.bind(this)); |
271 | 211 |
272 this.onInitialized_(remoting.Error.NONE, this.plugin_); | 212 this.onInitialized_(remoting.Error.NONE, this.plugin_); |
273 }; | 213 }; |
274 | 214 |
275 /** | 215 /** |
276 * This is a callback that gets called when the plugin notifies us of a change | |
277 * in the size of the remote desktop. | |
278 * | |
279 * @return {void} Nothing. | |
280 * @private | |
281 */ | |
282 remoting.DesktopConnectedView.prototype.onDesktopSizeChanged_ = function() { | |
283 var desktop = this.plugin_.hostDesktop().getDimensions(); | |
284 console.log('desktop size changed: ' + | |
285 desktop.width + 'x' + | |
286 desktop.height +' @ ' + | |
287 desktop.xDpi + 'x' + | |
288 desktop.yDpi + ' DPI'); | |
289 this.updateDimensions(); | |
290 this.updateScrollbarVisibility(); | |
291 }; | |
292 | |
293 /** | |
294 * Sets the non-click-through area of the client in response to notifications | |
295 * from the plugin of desktop shape changes. | |
296 * | |
297 * @param {Array<Array<number>>} rects List of rectangles comprising the | |
298 * desktop shape. | |
299 * @return {void} Nothing. | |
300 * @private | |
301 */ | |
302 remoting.DesktopConnectedView.prototype.onDesktopShapeChanged_ = function( | |
303 rects) { | |
304 // Build the list of rects for the input region. | |
305 var inputRegion = []; | |
306 for (var i = 0; i < rects.length; ++i) { | |
307 var rect = {}; | |
308 rect.left = rects[i][0]; | |
309 rect.top = rects[i][1]; | |
310 rect.width = rects[i][2]; | |
311 rect.height = rects[i][3]; | |
312 inputRegion.push(rect); | |
313 } | |
314 | |
315 remoting.windowShape.setDesktopRects(inputRegion); | |
316 }; | |
317 | |
318 /** | |
319 * This is a callback that gets called when the window is resized. | 216 * This is a callback that gets called when the window is resized. |
320 * | 217 * |
321 * @return {void} Nothing. | 218 * @return {void} Nothing. |
322 */ | 219 */ |
323 remoting.DesktopConnectedView.prototype.onResize = function() { | 220 remoting.DesktopConnectedView.prototype.onResize = function() { |
324 this.updateDimensions(); | 221 if (this.viewport_) { |
325 | 222 this.viewport_.onResize(); |
326 if (this.notifyClientResolutionTimer_) { | |
327 window.clearTimeout(this.notifyClientResolutionTimer_); | |
328 this.notifyClientResolutionTimer_ = null; | |
329 } | 223 } |
330 | |
331 // Defer notifying the host of the change until the window stops resizing, to | |
332 // avoid overloading the control channel with notifications. | |
333 if (this.getResizeToClient()) { | |
334 var kResizeRateLimitMs = 250; | |
335 var clientArea = this.getClientArea_(); | |
336 this.notifyClientResolutionTimer_ = window.setTimeout( | |
337 this.notifyClientResolution_.bind(this), | |
338 kResizeRateLimitMs); | |
339 } | |
340 | |
341 // If bump-scrolling is enabled, adjust the plugin margins to fully utilize | |
342 // the new window area. | |
343 this.resetScroll_(); | |
344 | |
345 this.updateScrollbarVisibility(); | |
346 }; | 224 }; |
347 | 225 |
348 /** | 226 /** |
349 * Callback that the plugin invokes to indicate when the connection is | 227 * Callback that the plugin invokes to indicate when the connection is |
350 * ready. | 228 * ready. |
351 * | 229 * |
352 * @param {boolean} ready True if the connection is ready. | 230 * @param {boolean} ready True if the connection is ready. |
353 */ | 231 */ |
354 remoting.DesktopConnectedView.prototype.onConnectionReady = function(ready) { | 232 remoting.DesktopConnectedView.prototype.onConnectionReady = function(ready) { |
355 if (!ready) { | 233 if (!ready) { |
356 this.container_.classList.add('session-client-inactive'); | 234 this.container_.classList.add('session-client-inactive'); |
357 } else { | 235 } else { |
358 this.container_.classList.remove('session-client-inactive'); | 236 this.container_.classList.remove('session-client-inactive'); |
359 } | 237 } |
360 }; | 238 }; |
361 | 239 |
362 /** | 240 /** |
363 * Deletes the <embed> element from the container, without sending a | 241 * Deletes the <embed> element from the container, without sending a |
364 * session_terminate request. This is to be called when the session was | 242 * session_terminate request. This is to be called when the session was |
365 * disconnected by the Host. | 243 * disconnected by the Host. |
366 * | 244 * |
367 * @return {void} Nothing. | 245 * @return {void} Nothing. |
368 */ | 246 */ |
369 remoting.DesktopConnectedView.prototype.removePlugin = function() { | 247 remoting.DesktopConnectedView.prototype.removePlugin = function() { |
370 if (this.plugin_) { | 248 if (this.plugin_) { |
371 base.dispose(this.eventHooks_); | |
372 this.eventHooks_ = null; | |
373 this.plugin_.element().removeEventListener( | 249 this.plugin_.element().removeEventListener( |
374 'focus', this.callPluginGotFocus_, false); | 250 'focus', this.callPluginGotFocus_, false); |
375 this.plugin_.element().removeEventListener( | 251 this.plugin_.element().removeEventListener( |
376 'blur', this.callPluginLostFocus_, false); | 252 'blur', this.callPluginLostFocus_, false); |
377 this.plugin_.dispose(); | 253 this.plugin_.dispose(); |
378 this.plugin_ = null; | 254 this.plugin_ = null; |
379 } | 255 } |
380 | 256 |
381 this.updateClientSessionUi_(null); | 257 this.updateClientSessionUi_(null); |
382 }; | 258 }; |
(...skipping 14 matching lines...) Expand all Loading... | |
397 if (remoting.optionsMenu) { | 273 if (remoting.optionsMenu) { |
398 remoting.optionsMenu.setDesktopConnectedView(null); | 274 remoting.optionsMenu.setDesktopConnectedView(null); |
399 } | 275 } |
400 | 276 |
401 document.body.classList.remove('connected'); | 277 document.body.classList.remove('connected'); |
402 this.container_.removeEventListener( | 278 this.container_.removeEventListener( |
403 'mousemove', this.updateMouseCursorPosition_, true); | 279 'mousemove', this.updateMouseCursorPosition_, true); |
404 // Stop listening for full-screen events. | 280 // Stop listening for full-screen events. |
405 remoting.fullscreen.removeListener(this.callOnFullScreenChanged_); | 281 remoting.fullscreen.removeListener(this.callOnFullScreenChanged_); |
406 | 282 |
283 base.dispose(this.viewport_); | |
284 this.viewport_ = null; | |
407 } else { | 285 } else { |
286 this.viewport_ = new remoting.DesktopViewport( | |
287 document.getElementById('scroller'), | |
288 this.plugin_.hostDesktop(), | |
289 this.host_.options); | |
408 if (remoting.windowFrame) { | 290 if (remoting.windowFrame) { |
409 remoting.windowFrame.setDesktopConnectedView(this); | 291 remoting.windowFrame.setDesktopConnectedView(this); |
410 } | 292 } |
411 if (remoting.toolbar) { | 293 if (remoting.toolbar) { |
412 remoting.toolbar.setDesktopConnectedView(this); | 294 remoting.toolbar.setDesktopConnectedView(this); |
413 } | 295 } |
414 if (remoting.optionsMenu) { | 296 if (remoting.optionsMenu) { |
415 remoting.optionsMenu.setDesktopConnectedView(this); | 297 remoting.optionsMenu.setDesktopConnectedView(this); |
416 } | 298 } |
417 | 299 |
418 if (this.getResizeToClient()) { | |
419 this.notifyClientResolution_(); | |
420 } | |
421 | |
422 document.body.classList.add('connected'); | 300 document.body.classList.add('connected'); |
423 this.container_.addEventListener( | 301 this.container_.addEventListener( |
424 'mousemove', this.updateMouseCursorPosition_, true); | 302 'mousemove', this.updateMouseCursorPosition_, true); |
425 // Activate full-screen related UX. | 303 // Activate full-screen related UX. |
426 remoting.fullscreen.addListener(this.callOnFullScreenChanged_); | 304 remoting.fullscreen.addListener(this.callOnFullScreenChanged_); |
427 this.onDesktopSizeChanged_(); | |
428 this.setFocusHandlers_(); | 305 this.setFocusHandlers_(); |
429 } | 306 } |
430 }; | 307 }; |
431 | 308 |
432 /** | 309 /** |
433 * @return {{top: number, left:number}} The top-left corner of the plugin. | |
434 */ | |
435 remoting.DesktopConnectedView.prototype.getPluginPositionForTesting = function( | |
436 ) { | |
437 var style = this.container_.style; | |
438 return { | |
439 top: parseFloat(style.marginTop), | |
440 left: parseFloat(style.marginLeft) | |
441 }; | |
442 }; | |
443 | |
444 /** | |
445 * Constrains the focus to the plugin element. | 310 * Constrains the focus to the plugin element. |
446 * @private | 311 * @private |
447 */ | 312 */ |
448 remoting.DesktopConnectedView.prototype.setFocusHandlers_ = function() { | 313 remoting.DesktopConnectedView.prototype.setFocusHandlers_ = function() { |
449 this.plugin_.element().addEventListener( | 314 this.plugin_.element().addEventListener( |
450 'focus', this.callPluginGotFocus_, false); | 315 'focus', this.callPluginGotFocus_, false); |
451 this.plugin_.element().addEventListener( | 316 this.plugin_.element().addEventListener( |
452 'blur', this.callPluginLostFocus_, false); | 317 'blur', this.callPluginLostFocus_, false); |
453 this.plugin_.element().focus(); | 318 this.plugin_.element().focus(); |
454 }; | 319 }; |
455 | 320 |
456 /** | 321 /** |
457 * Set the shrink-to-fit and resize-to-client flags and save them if this is | 322 * Set the shrink-to-fit and resize-to-client flags and save them if this is |
458 * a Me2Me connection. | 323 * a Me2Me connection. |
459 * | 324 * |
460 * @param {boolean} shrinkToFit True if the remote desktop should be scaled | 325 * @param {boolean} shrinkToFit True if the remote desktop should be scaled |
461 * down if it is larger than the client window; false if scroll-bars | 326 * down if it is larger than the client window; false if scroll-bars |
462 * should be added in this case. | 327 * should be added in this case. |
463 * @param {boolean} resizeToClient True if window resizes should cause the | 328 * @param {boolean} resizeToClient True if window resizes should cause the |
464 * host to attempt to resize its desktop to match the client window size; | 329 * host to attempt to resize its desktop to match the client window size; |
465 * false to disable this behaviour for subsequent window resizes--the | 330 * false to disable this behaviour for subsequent window resizes--the |
466 * current host desktop size is not restored in this case. | 331 * current host desktop size is not restored in this case. |
467 * @return {void} Nothing. | 332 * @return {void} Nothing. |
468 */ | 333 */ |
469 remoting.DesktopConnectedView.prototype.setScreenMode = | 334 remoting.DesktopConnectedView.prototype.setScreenMode = |
470 function(shrinkToFit, resizeToClient) { | 335 function(shrinkToFit, resizeToClient) { |
471 if (resizeToClient && !this.getResizeToClient()) { | 336 this.viewport_.setScreenMode(shrinkToFit, resizeToClient); |
472 this.notifyClientResolution_(); | |
473 } | |
474 | |
475 // If enabling shrink, reset bump-scroll offsets. | |
476 var needsScrollReset = shrinkToFit && !this.getShrinkToFit(); | |
477 | |
478 this.host_.options.shrinkToFit = shrinkToFit; | |
479 this.host_.options.resizeToClient = resizeToClient; | |
480 this.updateScrollbarVisibility(); | |
481 this.host_.options.save(); | |
482 | |
483 this.updateDimensions(); | |
484 if (needsScrollReset) { | |
485 this.resetScroll_(); | |
486 } | |
487 }; | 337 }; |
488 | 338 |
489 /** | 339 /** |
490 * Sets and stores the scale factor to apply to host sizing requests. | |
491 * The desktopScale applies to the dimensions reported to the host, not | |
492 * to the client DPI reported to it. | |
493 * | |
494 * @param {number} desktopScale Scale factor to apply. | |
495 */ | |
496 remoting.DesktopConnectedView.prototype.setDesktopScale = function( | |
497 desktopScale) { | |
498 this.host_.options.desktopScale = desktopScale; | |
499 | |
500 // onResize() will update the plugin size and scrollbars for the new | |
501 // scaled plugin dimensions, and send a client resolution notification. | |
502 this.onResize(); | |
503 | |
504 // Save the new desktop scale setting. | |
505 this.host_.options.save(); | |
506 }; | |
507 | |
508 /** | |
509 * Called when the full-screen status has changed, either via the | 340 * Called when the full-screen status has changed, either via the |
510 * remoting.Fullscreen class, or via a system event such as the Escape key | 341 * remoting.Fullscreen class, or via a system event such as the Escape key |
511 * | 342 * |
512 * @param {boolean=} fullscreen True if the app is entering full-screen mode; | 343 * @param {boolean=} fullscreen True if the app is entering full-screen mode; |
513 * false if it is leaving it. | 344 * false if it is leaving it. |
514 * @private | 345 * @private |
515 */ | 346 */ |
516 remoting.DesktopConnectedView.prototype.onFullScreenChanged_ = function ( | 347 remoting.DesktopConnectedView.prototype.onFullScreenChanged_ = function ( |
517 fullscreen) { | 348 fullscreen) { |
518 this.enableBumpScroll_(fullscreen); | 349 if (this.viewport_) { |
350 this.viewport_.enableBumpScroll(Boolean(fullscreen)); | |
351 } | |
519 }; | 352 }; |
520 | 353 |
521 /** | 354 /** |
522 * Callback function called when the plugin element gets focus. | 355 * Callback function called when the plugin element gets focus. |
523 */ | 356 */ |
524 remoting.DesktopConnectedView.prototype.pluginGotFocus_ = function() { | 357 remoting.DesktopConnectedView.prototype.pluginGotFocus_ = function() { |
525 remoting.clipboard.initiateToHost(); | 358 remoting.clipboard.initiateToHost(); |
526 }; | 359 }; |
527 | 360 |
528 /** | 361 /** |
(...skipping 22 matching lines...) Expand all Loading... | |
551 function(url, hotspotX, hotspotY) { | 384 function(url, hotspotX, hotspotY) { |
552 this.mouseCursorOverlay_.hidden = !url; | 385 this.mouseCursorOverlay_.hidden = !url; |
553 if (url) { | 386 if (url) { |
554 this.mouseCursorOverlay_.style.marginLeft = '-' + hotspotX + 'px'; | 387 this.mouseCursorOverlay_.style.marginLeft = '-' + hotspotX + 'px'; |
555 this.mouseCursorOverlay_.style.marginTop = '-' + hotspotY + 'px'; | 388 this.mouseCursorOverlay_.style.marginTop = '-' + hotspotY + 'px'; |
556 this.mouseCursorOverlay_.src = url; | 389 this.mouseCursorOverlay_.src = url; |
557 } | 390 } |
558 }; | 391 }; |
559 | 392 |
560 /** | 393 /** |
561 * Enable or disable bump-scrolling. When disabling bump scrolling, also reset | |
562 * the scroll offsets to (0, 0). | |
563 * @param {boolean=} enable True to enable bump-scrolling, false to disable it. | |
564 * @private | |
565 */ | |
566 remoting.DesktopConnectedView.prototype.enableBumpScroll_ = function(enable) { | |
567 var element = /** @type{HTMLElement} */ (document.documentElement); | |
568 if (enable) { | |
569 /** @type {null|function(Event):void} */ | |
570 this.onMouseMoveRef_ = this.onMouseMove_.bind(this); | |
571 element.addEventListener('mousemove', this.onMouseMoveRef_, false); | |
572 } else { | |
573 element.removeEventListener('mousemove', this.onMouseMoveRef_, false); | |
574 this.onMouseMoveRef_ = null; | |
575 this.resetScroll_(); | |
576 } | |
577 }; | |
578 | |
579 remoting.DesktopConnectedView.prototype.resetScroll_ = function() { | |
580 this.container_.style.marginTop = '0px'; | |
581 this.container_.style.marginLeft = '0px'; | |
582 }; | |
583 | |
584 /** | |
585 * @param {Event} event The mouse event. | |
586 * @private | |
587 */ | |
588 remoting.DesktopConnectedView.prototype.onMouseMove_ = function(event) { | |
589 if (this.bumpScrollTimer_) { | |
590 window.clearTimeout(this.bumpScrollTimer_); | |
591 this.bumpScrollTimer_ = null; | |
592 } | |
593 | |
594 /** | |
595 * Compute the scroll speed based on how close the mouse is to the edge. | |
596 * @param {number} mousePos The mouse x- or y-coordinate | |
597 * @param {number} size The width or height of the content area. | |
598 * @return {number} The scroll delta, in pixels. | |
599 */ | |
600 var computeDelta = function(mousePos, size) { | |
601 var threshold = 10; | |
602 if (mousePos >= size - threshold) { | |
603 return 1 + 5 * (mousePos - (size - threshold)) / threshold; | |
604 } else if (mousePos <= threshold) { | |
605 return -1 - 5 * (threshold - mousePos) / threshold; | |
606 } | |
607 return 0; | |
608 }; | |
609 | |
610 var clientArea = this.getClientArea_(); | |
611 var dx = computeDelta(event.x, clientArea.width); | |
612 var dy = computeDelta(event.y, clientArea.height); | |
613 | |
614 if (dx != 0 || dy != 0) { | |
615 this.raiseEvent(remoting.DesktopConnectedView.Events.bumpScrollStarted); | |
616 /** @type {remoting.DesktopConnectedView} */ | |
617 var that = this; | |
618 /** | |
619 * Scroll the view, and schedule a timer to do so again unless we've hit | |
620 * the edges of the screen. This timer is cancelled when the mouse moves. | |
621 * @param {number} expected The time at which we expect to be called. | |
622 */ | |
623 var repeatScroll = function(expected) { | |
624 /** @type {number} */ | |
625 var now = new Date().getTime(); | |
626 /** @type {number} */ | |
627 var timeout = 10; | |
628 var lateAdjustment = 1 + (now - expected) / timeout; | |
629 if (that.scroll_(lateAdjustment * dx, lateAdjustment * dy)) { | |
630 that.raiseEvent(remoting.DesktopConnectedView.Events.bumpScrollStopped); | |
631 } else { | |
632 that.bumpScrollTimer_ = window.setTimeout( | |
633 function() { repeatScroll(now + timeout); }, | |
634 timeout); | |
635 } | |
636 }; | |
637 repeatScroll(new Date().getTime()); | |
638 } | |
639 }; | |
640 | |
641 /** | |
642 * Scroll the client plugin by the specified amount, keeping it visible. | |
643 * Note that this is only used in content full-screen mode (not windowed or | |
644 * browser full-screen modes), where window.scrollBy and the scrollTop and | |
645 * scrollLeft properties don't work. | |
646 * @param {number} dx The amount by which to scroll horizontally. Positive to | |
647 * scroll right; negative to scroll left. | |
648 * @param {number} dy The amount by which to scroll vertically. Positive to | |
649 * scroll down; negative to scroll up. | |
650 * @return {boolean} True if the requested scroll had no effect because both | |
651 * vertical and horizontal edges of the screen have been reached. | |
652 * @private | |
653 */ | |
654 remoting.DesktopConnectedView.prototype.scroll_ = function(dx, dy) { | |
655 /** | |
656 * Helper function for x- and y-scrolling | |
657 * @param {number|string} curr The current margin, eg. "10px". | |
658 * @param {number} delta The requested scroll amount. | |
659 * @param {number} windowBound The size of the window, in pixels. | |
660 * @param {number} pluginBound The size of the plugin, in pixels. | |
661 * @param {{stop: boolean}} stop Reference parameter used to indicate when | |
662 * the scroll has reached one of the edges and can be stopped in that | |
663 * direction. | |
664 * @return {string} The new margin value. | |
665 */ | |
666 var adjustMargin = function(curr, delta, windowBound, pluginBound, stop) { | |
667 var minMargin = Math.min(0, windowBound - pluginBound); | |
668 var result = (curr ? parseFloat(curr) : 0) - delta; | |
669 result = Math.min(0, Math.max(minMargin, result)); | |
670 stop.stop = (result == 0 || result == minMargin); | |
671 return result + 'px'; | |
672 }; | |
673 | |
674 var plugin = this.plugin_.element(); | |
675 var style = this.container_.style; | |
676 | |
677 var stopX = { stop: false }; | |
678 var clientArea = this.getClientArea_(); | |
679 style.marginLeft = adjustMargin(style.marginLeft, dx, clientArea.width, | |
680 this.pluginWidthForBumpScrollTesting_ || plugin.clientWidth, stopX); | |
681 | |
682 var stopY = { stop: false }; | |
683 style.marginTop = adjustMargin( | |
684 style.marginTop, dy, clientArea.height, | |
685 this.pluginHeightForBumpScrollTesting_ || plugin.clientHeight, stopY); | |
686 return stopX.stop && stopY.stop; | |
687 }; | |
688 | |
689 /** | |
690 * Refreshes the plugin's dimensions, taking into account the sizes of the | |
691 * remote desktop and client window, and the current scale-to-fit setting. | |
692 * | |
693 * @return {void} Nothing. | |
694 */ | |
695 remoting.DesktopConnectedView.prototype.updateDimensions = function() { | |
696 var desktopSize = this.plugin_.hostDesktop().getDimensions(); | |
697 | |
698 if (desktopSize.width === 0 || | |
699 desktopSize.height === 0) { | |
700 return; | |
701 } | |
702 | |
703 var desktopDpi = { x: desktopSize.xDpi, | |
704 y: desktopSize.yDpi }; | |
705 var newSize = remoting.DesktopConnectedView.choosePluginSize( | |
706 this.getClientArea_(), window.devicePixelRatio, | |
707 desktopSize, desktopDpi, this.host_.options.desktopScale, | |
708 remoting.fullscreen.isActive(), this.getShrinkToFit()); | |
709 | |
710 // Resize the plugin if necessary. | |
711 console.log('plugin dimensions:' + newSize.width + 'x' + newSize.height); | |
712 this.plugin_.element().style.width = newSize.width + 'px'; | |
713 this.plugin_.element().style.height = newSize.height + 'px'; | |
714 | |
715 // When we receive the first plugin dimensions from the host, we know that | |
716 // remote host has started. | |
717 remoting.app.onVideoStreamingStarted(); | |
718 } | |
719 | |
720 /** | |
721 * Helper function accepting client and host dimensions, and returning a chosen | |
722 * size for the plugin element, in DIPs. | |
723 * | |
724 * @param {{width: number, height: number}} clientSizeDips Available client | |
725 * dimensions, in DIPs. | |
726 * @param {number} clientPixelRatio Number of physical pixels per client DIP. | |
727 * @param {{width: number, height: number}} desktopSize Size of the host desktop | |
728 * in physical pixels. | |
729 * @param {{x: number, y: number}} desktopDpi DPI of the host desktop in both | |
730 * dimensions. | |
731 * @param {number} desktopScale The scale factor configured for the host. | |
732 * @param {boolean} isFullscreen True if full-screen mode is active. | |
733 * @param {boolean} shrinkToFit True if shrink-to-fit should be applied. | |
734 * @return {{width: number, height: number}} Chosen plugin dimensions, in DIPs. | |
735 */ | |
736 remoting.DesktopConnectedView.choosePluginSize = function( | |
737 clientSizeDips, clientPixelRatio, desktopSize, desktopDpi, desktopScale, | |
738 isFullscreen, shrinkToFit) { | |
739 base.debug.assert(clientSizeDips.width > 0); | |
740 base.debug.assert(clientSizeDips.height > 0); | |
741 base.debug.assert(clientPixelRatio >= 1.0); | |
742 base.debug.assert(desktopSize.width > 0); | |
743 base.debug.assert(desktopSize.height > 0); | |
744 base.debug.assert(desktopDpi.x > 0); | |
745 base.debug.assert(desktopDpi.y > 0); | |
746 base.debug.assert(desktopScale > 0); | |
747 | |
748 // We have the following goals in sizing the desktop display at the client: | |
749 // 1. Avoid losing detail by down-scaling beyond 1:1 host:device pixels. | |
750 // 2. Avoid up-scaling if that will cause the client to need scrollbars. | |
751 // 3. Avoid introducing blurriness with non-integer up-scaling factors. | |
752 // 4. Avoid having huge "letterboxes" around the desktop, if it's really | |
753 // small. | |
754 // 5. Compensate for mismatched DPIs, so that the behaviour of features like | |
755 // shrink-to-fit matches their "natural" rather than their pixel size. | |
756 // e.g. with shrink-to-fit active a 1024x768 low-DPI host on a 640x480 | |
757 // high-DPI client will be up-scaled to 1280x960, rather than displayed | |
758 // at 1:1 host:physical client pixels. | |
759 // | |
760 // To determine the ideal size we follow a four-stage process: | |
761 // 1. Determine the "natural" size at which to display the desktop. | |
762 // a. Initially assume 1:1 mapping of desktop to client device pixels. | |
763 // b. If host DPI is less than the client's then up-scale accordingly. | |
764 // c. If desktopScale is configured for the host then allow that to | |
765 // reduce the amount of up-scaling from (b). e.g. if the client:host | |
766 // DPIs are 2:1 then a desktopScale of 1.5 would reduce the up-scale | |
767 // to 4:3, while a desktopScale of 3.0 would result in no up-scaling. | |
768 // 2. If the natural size of the desktop is smaller than the client device | |
769 // then apply up-scaling by an integer scale factor to avoid excessive | |
770 // letterboxing. | |
771 // 3. If shrink-to-fit is configured then: | |
772 // a. If the natural size exceeds the client size then apply down-scaling | |
773 // by an arbitrary scale factor. | |
774 // b. If we're in full-screen mode and the client & host aspect-ratios | |
775 // are radically different (e.g. the host is actually multi-monitor) | |
776 // then shrink-to-fit to the shorter dimension, rather than leaving | |
777 // huge letterboxes; the user can then bump-scroll around the desktop. | |
778 // 4. If the overall scale factor is fractionally over an integer factor | |
779 // then reduce it to that integer factor, to avoid blurring. | |
780 | |
781 // All calculations are performed in device pixels. | |
782 var clientWidth = clientSizeDips.width * clientPixelRatio; | |
783 var clientHeight = clientSizeDips.height * clientPixelRatio; | |
784 | |
785 // 1. Determine a "natural" size at which to display the desktop. | |
786 var scale = 1.0; | |
787 | |
788 // Determine the effective host device pixel ratio. | |
789 // Note that we round up or down to the closest integer pixel ratio. | |
790 var hostPixelRatioX = Math.round(desktopDpi.x / 96); | |
791 var hostPixelRatioY = Math.round(desktopDpi.y / 96); | |
792 var hostPixelRatio = Math.min(hostPixelRatioX, hostPixelRatioY); | |
793 | |
794 // Allow up-scaling to account for DPI. | |
795 scale = Math.max(scale, clientPixelRatio / hostPixelRatio); | |
796 | |
797 // Allow some or all of the up-scaling to be cancelled by the desktopScale. | |
798 if (desktopScale > 1.0) { | |
799 scale = Math.max(1.0, scale / desktopScale); | |
800 } | |
801 | |
802 // 2. If the host is still much smaller than the client, then up-scale to | |
803 // avoid wasting space, but only by an integer factor, to avoid blurring. | |
804 if (desktopSize.width * scale <= clientWidth && | |
805 desktopSize.height * scale <= clientHeight) { | |
806 var scaleX = Math.floor(clientWidth / desktopSize.width); | |
807 var scaleY = Math.floor(clientHeight / desktopSize.height); | |
808 scale = Math.min(scaleX, scaleY); | |
809 base.debug.assert(scale >= 1.0); | |
810 } | |
811 | |
812 // 3. Apply shrink-to-fit, if configured. | |
813 if (shrinkToFit) { | |
814 var scaleFitWidth = Math.min(scale, clientWidth / desktopSize.width); | |
815 var scaleFitHeight = Math.min(scale, clientHeight / desktopSize.height); | |
816 scale = Math.min(scaleFitHeight, scaleFitWidth); | |
817 | |
818 // If we're running full-screen then try to handle common side-by-side | |
819 // multi-monitor combinations more intelligently. | |
820 if (isFullscreen) { | |
821 // If the host has two monitors each the same size as the client then | |
822 // scale-to-fit will have the desktop occupy only 50% of the client area, | |
823 // in which case it would be preferable to down-scale less and let the | |
824 // user bump-scroll around ("scale-and-pan"). | |
825 // Triggering scale-and-pan if less than 65% of the client area would be | |
826 // used adds enough fuzz to cope with e.g. 1280x800 client connecting to | |
827 // a (2x1280)x1024 host nicely. | |
828 // Note that we don't need to account for scrollbars while fullscreen. | |
829 if (scale <= scaleFitHeight * 0.65) { | |
830 scale = scaleFitHeight; | |
831 } | |
832 if (scale <= scaleFitWidth * 0.65) { | |
833 scale = scaleFitWidth; | |
834 } | |
835 } | |
836 } | |
837 | |
838 // 4. Avoid blurring for close-to-integer up-scaling factors. | |
839 if (scale > 1.0) { | |
840 var scaleBlurriness = scale / Math.floor(scale); | |
841 if (scaleBlurriness < 1.1) { | |
842 scale = Math.floor(scale); | |
843 } | |
844 } | |
845 | |
846 // Return the necessary plugin dimensions in DIPs. | |
847 scale = scale / clientPixelRatio; | |
848 var pluginWidth = Math.round(desktopSize.width * scale); | |
849 var pluginHeight = Math.round(desktopSize.height * scale); | |
850 return { width: pluginWidth, height: pluginHeight }; | |
851 } | |
852 | |
853 /** | |
854 * Called when the window or desktop size or the scaling settings change, | |
855 * to set the scroll-bar visibility. | |
856 * | |
857 * TODO(jamiewalch): crbug.com/252796: Remove this once crbug.com/240772 is | |
858 * fixed. | |
859 */ | |
860 remoting.DesktopConnectedView.prototype.updateScrollbarVisibility = function() { | |
861 var scroller = document.getElementById('scroller'); | |
862 if (!scroller) { | |
863 return; | |
864 } | |
865 | |
866 var needsVerticalScroll = false; | |
867 var needsHorizontalScroll = false; | |
868 if (!this.getShrinkToFit()) { | |
869 // Determine whether or not horizontal or vertical scrollbars are | |
870 // required, taking into account their width. | |
871 var clientArea = this.getClientArea_(); | |
872 var desktopSize = this.plugin_.hostDesktop().getDimensions(); | |
873 needsVerticalScroll = clientArea.height < desktopSize.height; | |
874 needsHorizontalScroll = clientArea.width < desktopSize.width; | |
875 var kScrollBarWidth = 16; | |
876 if (needsHorizontalScroll && !needsVerticalScroll) { | |
877 needsVerticalScroll = | |
878 clientArea.height - kScrollBarWidth < desktopSize.height; | |
879 } else if (!needsHorizontalScroll && needsVerticalScroll) { | |
880 needsHorizontalScroll = | |
881 clientArea.width - kScrollBarWidth < desktopSize.width; | |
882 } | |
883 } | |
884 | |
885 if (needsHorizontalScroll) { | |
886 scroller.classList.remove('no-horizontal-scroll'); | |
887 } else { | |
888 scroller.classList.add('no-horizontal-scroll'); | |
889 } | |
890 if (needsVerticalScroll) { | |
891 scroller.classList.remove('no-vertical-scroll'); | |
892 } else { | |
893 scroller.classList.add('no-vertical-scroll'); | |
894 } | |
895 }; | |
896 | |
897 /** | |
898 * Sets and stores the key remapping setting for the current host. | 394 * Sets and stores the key remapping setting for the current host. |
899 * | 395 * |
900 * @param {string} remappings Comma separated list of key remappings. | 396 * @param {string} remappings Comma separated list of key remappings. |
901 */ | 397 */ |
902 remoting.DesktopConnectedView.prototype.setRemapKeys = function(remappings) { | 398 remoting.DesktopConnectedView.prototype.setRemapKeys = function(remappings) { |
903 // Cancel any existing remappings and apply the new ones. | 399 // Cancel any existing remappings and apply the new ones. |
904 this.applyRemapKeys_(false); | 400 this.applyRemapKeys_(false); |
905 this.host_.options.remapKeys = remappings; | 401 this.host_.options.remapKeys = remappings; |
906 this.applyRemapKeys_(true); | 402 this.applyRemapKeys_(true); |
907 | 403 |
(...skipping 138 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1046 * @param {Object} message The parsed extension message data. | 542 * @param {Object} message The parsed extension message data. |
1047 * @return {boolean} True if the message was recognized, false otherwise. | 543 * @return {boolean} True if the message was recognized, false otherwise. |
1048 */ | 544 */ |
1049 remoting.DesktopConnectedView.prototype.handleExtensionMessage = | 545 remoting.DesktopConnectedView.prototype.handleExtensionMessage = |
1050 function(type, message) { | 546 function(type, message) { |
1051 if (this.videoFrameRecorder_) { | 547 if (this.videoFrameRecorder_) { |
1052 return this.videoFrameRecorder_.handleMessage(type, message); | 548 return this.videoFrameRecorder_.handleMessage(type, message); |
1053 } | 549 } |
1054 return false; | 550 return false; |
1055 }; | 551 }; |
OLD | NEW |