15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "remoting/host/resizing_host_observer.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include <list> 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/bind.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h" 119ab5563a3196760eb381d102cbb2bc0f7abc6a50Ben Murdoch#include "base/message_loop/message_loop.h" 122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "remoting/host/desktop_resizer.h" 132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "remoting/host/screen_resolution.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 150f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)namespace remoting { 162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)namespace { 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 18868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Minimum amount of time to wait between desktop resizes. Note that this 19868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// constant is duplicated by the ResizingHostObserverTest.RateLimited 20868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// unit-test and must be kept in sync. 21868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)const int kMinimumResizeIntervalMs = 1000; 22868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 230f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)class CandidateResolution { 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 250f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) CandidateResolution(const ScreenResolution& candidate, 260f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) const ScreenResolution& preferred) 270f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) : resolution_(candidate) { 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Protect against division by zero. 290f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) CHECK(!candidate.IsEmpty()); 300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) DCHECK(!preferred.IsEmpty()); 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The client scale factor is the smaller of the candidate:preferred ratios 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // for width and height. 340f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) if ((candidate.dimensions().width() > preferred.dimensions().width()) || 350f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) (candidate.dimensions().height() > preferred.dimensions().height())) { 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const float width_ratio = 370f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) static_cast<float>(preferred.dimensions().width()) / 380f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) candidate.dimensions().width(); 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const float height_ratio = 400f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) static_cast<float>(preferred.dimensions().height()) / 410f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) candidate.dimensions().height(); 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) client_scale_factor_ = std::min(width_ratio, height_ratio); 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Since clients do not scale up, 1.0 is the maximum. 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) client_scale_factor_ = 1.0; 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The aspect ratio "goodness" is defined as being the ratio of the smaller 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // of the two aspect ratios (candidate and preferred) to the larger. The 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // best aspect ratio is the one that most closely matches the preferred 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // aspect ratio (in other words, the ideal aspect ratio "goodness" is 1.0). 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // By keeping the values < 1.0, it allows ratios that differ in opposite 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // directions to be compared numerically. 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float candidate_aspect_ratio = 550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) static_cast<float>(candidate.dimensions().width()) / 560f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) candidate.dimensions().height(); 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float preferred_aspect_ratio = 580f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) static_cast<float>(preferred.dimensions().width()) / 590f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) preferred.dimensions().height(); 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (candidate_aspect_ratio > preferred_aspect_ratio) { 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) aspect_ratio_goodness_ = preferred_aspect_ratio / candidate_aspect_ratio; 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) aspect_ratio_goodness_ = candidate_aspect_ratio / preferred_aspect_ratio; 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 670f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) const ScreenResolution& resolution() const { return resolution_; } 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float client_scale_factor() const { return client_scale_factor_; } 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float aspect_ratio_goodness() const { return aspect_ratio_goodness_; } 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int64 area() const { 710f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) return static_cast<int64>(resolution_.dimensions().width()) * 720f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) resolution_.dimensions().height(); 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 750f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // TODO(jamiewalch): Also compare the DPI: http://crbug.com/172405 760f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) bool IsBetterThan(const CandidateResolution& other) const { 770f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // If either resolution would require down-scaling, prefer the one that 780f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // down-scales the least (since the client scale factor is at most 1.0, 790f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // this does not differentiate between resolutions that don't require 800f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // down-scaling). 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (client_scale_factor() < other.client_scale_factor()) { 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (client_scale_factor() > other.client_scale_factor()) { 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 870f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // If the scale factors are the same, pick the resolution with the largest 880f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // area. 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (area() < other.area()) { 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (area() > other.area()) { 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 950f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // If the areas are equal, pick the resolution with the "best" aspect ratio. 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (aspect_ratio_goodness() < other.aspect_ratio_goodness()) { 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return false; 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (aspect_ratio_goodness() > other.aspect_ratio_goodness()) { 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return true; 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1020f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // All else being equal (for example, comparing 640x480 to 480x640 w.r.t. 1030f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // 640x640), just pick the widest, since desktop UIs are typically designed 1040f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // for landscape aspect ratios. 1050f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) return resolution().dimensions().width() > 1060f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) other.resolution().dimensions().width(); 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float client_scale_factor_; 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) float aspect_ratio_goodness_; 1120f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) ScreenResolution resolution_; 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} // namespace 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)ResizingHostObserver::ResizingHostObserver( 1182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) scoped_ptr<DesktopResizer> desktop_resizer) 1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) : desktop_resizer_(desktop_resizer.Pass()), 120868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) now_function_(base::Bind(base::Time::Now)), 121868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) weak_factory_(this) { 1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)ResizingHostObserver::~ResizingHostObserver() { 1250f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) if (!original_resolution_.IsEmpty()) 1260f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) desktop_resizer_->RestoreResolution(original_resolution_); 1272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)} 1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) 1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)void ResizingHostObserver::SetScreenResolution( 1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) const ScreenResolution& resolution) { 131868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // Get the current time. This function is called exactly once for each call 132868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // to SetScreenResolution to simplify the implementation of unit-tests. 133868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::Time now = now_function_.Run(); 134868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 1352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if (resolution.IsEmpty()) 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 138868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // Resizing the desktop too often is probably not a good idea, so apply a 139868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // simple rate-limiting scheme. 140868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::TimeDelta minimum_resize_interval = 141868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs); 142868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::Time next_allowed_resize = 143868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) previous_resize_time_ + minimum_resize_interval; 144868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 145868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) if (now < next_allowed_resize) { 146868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) deferred_resize_timer_.Start( 147868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) FROM_HERE, 148868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) next_allowed_resize - now, 149868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) base::Bind(&ResizingHostObserver::SetScreenResolution, 150868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) weak_factory_.GetWeakPtr(), resolution)); 151868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) return; 152868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 153868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 1540f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // If the implementation returns any resolutions, pick the best one according 1550f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) // to the algorithm described in CandidateResolution::IsBetterThen. 1560f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) std::list<ScreenResolution> resolutions = 1570f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) desktop_resizer_->GetSupportedResolutions(resolution); 1580f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) if (resolutions.empty()) 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1600f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) CandidateResolution best_candidate(resolutions.front(), resolution); 1610f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) for (std::list<ScreenResolution>::const_iterator i = ++resolutions.begin(); 1620f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) i != resolutions.end(); ++i) { 1630f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) CandidateResolution candidate(*i, resolution); 1640f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) if (candidate.IsBetterThan(best_candidate)) { 1650f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) best_candidate = candidate; 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1680f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) ScreenResolution current_resolution = 1690f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) desktop_resizer_->GetCurrentResolution(); 17023730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) 17123730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) if (!best_candidate.resolution().Equals(current_resolution)) { 17223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) if (original_resolution_.IsEmpty()) 17323730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) original_resolution_ = current_resolution; 1740f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles) desktop_resizer_->SetResolution(best_candidate.resolution()); 17523730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) } 176868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 177868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) // Update the time of last resize to allow it to be rate-limited. 178868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) previous_resize_time_ = now; 179868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)} 180868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 181868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)void ResizingHostObserver::SetNowFunctionForTesting( 182868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) const base::Callback<base::Time(void)>& now_function) { 183868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) now_function_ = now_function; 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace remoting 187