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 3// found in the LICENSE file. 4 5#include "remoting/host/resizing_host_observer.h" 6 7#include <list> 8 9#include "base/bind.h" 10#include "base/logging.h" 11#include "base/message_loop/message_loop.h" 12#include "remoting/host/desktop_resizer.h" 13#include "remoting/host/screen_resolution.h" 14 15namespace remoting { 16namespace { 17 18// Minimum amount of time to wait between desktop resizes. Note that this 19// constant is duplicated by the ResizingHostObserverTest.RateLimited 20// unit-test and must be kept in sync. 21const int kMinimumResizeIntervalMs = 1000; 22 23class CandidateResolution { 24 public: 25 CandidateResolution(const ScreenResolution& candidate, 26 const ScreenResolution& preferred) 27 : resolution_(candidate) { 28 // Protect against division by zero. 29 CHECK(!candidate.IsEmpty()); 30 DCHECK(!preferred.IsEmpty()); 31 32 // The client scale factor is the smaller of the candidate:preferred ratios 33 // for width and height. 34 if ((candidate.dimensions().width() > preferred.dimensions().width()) || 35 (candidate.dimensions().height() > preferred.dimensions().height())) { 36 const float width_ratio = 37 static_cast<float>(preferred.dimensions().width()) / 38 candidate.dimensions().width(); 39 const float height_ratio = 40 static_cast<float>(preferred.dimensions().height()) / 41 candidate.dimensions().height(); 42 client_scale_factor_ = std::min(width_ratio, height_ratio); 43 } else { 44 // Since clients do not scale up, 1.0 is the maximum. 45 client_scale_factor_ = 1.0; 46 } 47 48 // The aspect ratio "goodness" is defined as being the ratio of the smaller 49 // of the two aspect ratios (candidate and preferred) to the larger. The 50 // best aspect ratio is the one that most closely matches the preferred 51 // aspect ratio (in other words, the ideal aspect ratio "goodness" is 1.0). 52 // By keeping the values < 1.0, it allows ratios that differ in opposite 53 // directions to be compared numerically. 54 float candidate_aspect_ratio = 55 static_cast<float>(candidate.dimensions().width()) / 56 candidate.dimensions().height(); 57 float preferred_aspect_ratio = 58 static_cast<float>(preferred.dimensions().width()) / 59 preferred.dimensions().height(); 60 if (candidate_aspect_ratio > preferred_aspect_ratio) { 61 aspect_ratio_goodness_ = preferred_aspect_ratio / candidate_aspect_ratio; 62 } else { 63 aspect_ratio_goodness_ = candidate_aspect_ratio / preferred_aspect_ratio; 64 } 65 } 66 67 const ScreenResolution& resolution() const { return resolution_; } 68 float client_scale_factor() const { return client_scale_factor_; } 69 float aspect_ratio_goodness() const { return aspect_ratio_goodness_; } 70 int64 area() const { 71 return static_cast<int64>(resolution_.dimensions().width()) * 72 resolution_.dimensions().height(); 73 } 74 75 // TODO(jamiewalch): Also compare the DPI: http://crbug.com/172405 76 bool IsBetterThan(const CandidateResolution& other) const { 77 // If either resolution would require down-scaling, prefer the one that 78 // down-scales the least (since the client scale factor is at most 1.0, 79 // this does not differentiate between resolutions that don't require 80 // down-scaling). 81 if (client_scale_factor() < other.client_scale_factor()) { 82 return false; 83 } else if (client_scale_factor() > other.client_scale_factor()) { 84 return true; 85 } 86 87 // If the scale factors are the same, pick the resolution with the largest 88 // area. 89 if (area() < other.area()) { 90 return false; 91 } else if (area() > other.area()) { 92 return true; 93 } 94 95 // If the areas are equal, pick the resolution with the "best" aspect ratio. 96 if (aspect_ratio_goodness() < other.aspect_ratio_goodness()) { 97 return false; 98 } else if (aspect_ratio_goodness() > other.aspect_ratio_goodness()) { 99 return true; 100 } 101 102 // All else being equal (for example, comparing 640x480 to 480x640 w.r.t. 103 // 640x640), just pick the widest, since desktop UIs are typically designed 104 // for landscape aspect ratios. 105 return resolution().dimensions().width() > 106 other.resolution().dimensions().width(); 107 } 108 109 private: 110 float client_scale_factor_; 111 float aspect_ratio_goodness_; 112 ScreenResolution resolution_; 113}; 114 115} // namespace 116 117ResizingHostObserver::ResizingHostObserver( 118 scoped_ptr<DesktopResizer> desktop_resizer) 119 : desktop_resizer_(desktop_resizer.Pass()), 120 now_function_(base::Bind(base::Time::Now)), 121 weak_factory_(this) { 122} 123 124ResizingHostObserver::~ResizingHostObserver() { 125 if (!original_resolution_.IsEmpty()) 126 desktop_resizer_->RestoreResolution(original_resolution_); 127} 128 129void ResizingHostObserver::SetScreenResolution( 130 const ScreenResolution& resolution) { 131 // Get the current time. This function is called exactly once for each call 132 // to SetScreenResolution to simplify the implementation of unit-tests. 133 base::Time now = now_function_.Run(); 134 135 if (resolution.IsEmpty()) 136 return; 137 138 // Resizing the desktop too often is probably not a good idea, so apply a 139 // simple rate-limiting scheme. 140 base::TimeDelta minimum_resize_interval = 141 base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs); 142 base::Time next_allowed_resize = 143 previous_resize_time_ + minimum_resize_interval; 144 145 if (now < next_allowed_resize) { 146 deferred_resize_timer_.Start( 147 FROM_HERE, 148 next_allowed_resize - now, 149 base::Bind(&ResizingHostObserver::SetScreenResolution, 150 weak_factory_.GetWeakPtr(), resolution)); 151 return; 152 } 153 154 // If the implementation returns any resolutions, pick the best one according 155 // to the algorithm described in CandidateResolution::IsBetterThen. 156 std::list<ScreenResolution> resolutions = 157 desktop_resizer_->GetSupportedResolutions(resolution); 158 if (resolutions.empty()) 159 return; 160 CandidateResolution best_candidate(resolutions.front(), resolution); 161 for (std::list<ScreenResolution>::const_iterator i = ++resolutions.begin(); 162 i != resolutions.end(); ++i) { 163 CandidateResolution candidate(*i, resolution); 164 if (candidate.IsBetterThan(best_candidate)) { 165 best_candidate = candidate; 166 } 167 } 168 ScreenResolution current_resolution = 169 desktop_resizer_->GetCurrentResolution(); 170 171 if (!best_candidate.resolution().Equals(current_resolution)) { 172 if (original_resolution_.IsEmpty()) 173 original_resolution_ = current_resolution; 174 desktop_resizer_->SetResolution(best_candidate.resolution()); 175 } 176 177 // Update the time of last resize to allow it to be rate-limited. 178 previous_resize_time_ = now; 179} 180 181void ResizingHostObserver::SetNowFunctionForTesting( 182 const base::Callback<base::Time(void)>& now_function) { 183 now_function_ = now_function; 184} 185 186} // namespace remoting 187