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 15 namespace remoting { 16 namespace { 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. 21 const int kMinimumResizeIntervalMs = 1000; 22 23 class 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 117 ResizingHostObserver::ResizingHostObserver( 118 scoped_ptr<DesktopResizer> desktop_resizer) 119 : desktop_resizer_(desktop_resizer.Pass()), 120 original_resolution_(desktop_resizer_->GetCurrentResolution()), 121 now_function_(base::Bind(base::Time::Now)), 122 weak_factory_(this) { 123 } 124 125 ResizingHostObserver::~ResizingHostObserver() { 126 if (!original_resolution_.IsEmpty()) 127 desktop_resizer_->RestoreResolution(original_resolution_); 128 } 129 130 void ResizingHostObserver::SetScreenResolution( 131 const ScreenResolution& resolution) { 132 // Get the current time. This function is called exactly once for each call 133 // to SetScreenResolution to simplify the implementation of unit-tests. 134 base::Time now = now_function_.Run(); 135 136 if (resolution.IsEmpty()) 137 return; 138 139 // Resizing the desktop too often is probably not a good idea, so apply a 140 // simple rate-limiting scheme. 141 base::TimeDelta minimum_resize_interval = 142 base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs); 143 base::Time next_allowed_resize = 144 previous_resize_time_ + minimum_resize_interval; 145 146 if (now < next_allowed_resize) { 147 deferred_resize_timer_.Start( 148 FROM_HERE, 149 next_allowed_resize - now, 150 base::Bind(&ResizingHostObserver::SetScreenResolution, 151 weak_factory_.GetWeakPtr(), resolution)); 152 return; 153 } 154 155 // If the implementation returns any resolutions, pick the best one according 156 // to the algorithm described in CandidateResolution::IsBetterThen. 157 std::list<ScreenResolution> resolutions = 158 desktop_resizer_->GetSupportedResolutions(resolution); 159 if (resolutions.empty()) 160 return; 161 CandidateResolution best_candidate(resolutions.front(), resolution); 162 for (std::list<ScreenResolution>::const_iterator i = ++resolutions.begin(); 163 i != resolutions.end(); ++i) { 164 CandidateResolution candidate(*i, resolution); 165 if (candidate.IsBetterThan(best_candidate)) { 166 best_candidate = candidate; 167 } 168 } 169 ScreenResolution current_resolution = 170 desktop_resizer_->GetCurrentResolution(); 171 if (!best_candidate.resolution().Equals(current_resolution)) 172 desktop_resizer_->SetResolution(best_candidate.resolution()); 173 174 // Update the time of last resize to allow it to be rate-limited. 175 previous_resize_time_ = now; 176 } 177 178 void ResizingHostObserver::SetNowFunctionForTesting( 179 const base::Callback<base::Time(void)>& now_function) { 180 now_function_ = now_function; 181 } 182 183 } // namespace remoting 184