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