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.
5 #include "remoting/host/resizing_host_observer.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"
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;
23 class CandidateResolution
{
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());
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
);
44 // Since clients do not scale up, 1.0 is the maximum.
45 client_scale_factor_
= 1.0;
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
;
63 aspect_ratio_goodness_
= candidate_aspect_ratio
/ preferred_aspect_ratio
;
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_
; }
71 return static_cast<int64
>(resolution_
.dimensions().width()) *
72 resolution_
.dimensions().height();
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
81 if (client_scale_factor() < other
.client_scale_factor()) {
83 } else if (client_scale_factor() > other
.client_scale_factor()) {
87 // If the scale factors are the same, pick the resolution with the largest
89 if (area() < other
.area()) {
91 } else if (area() > other
.area()) {
95 // If the areas are equal, pick the resolution with the "best" aspect ratio.
96 if (aspect_ratio_goodness() < other
.aspect_ratio_goodness()) {
98 } else if (aspect_ratio_goodness() > other
.aspect_ratio_goodness()) {
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();
110 float client_scale_factor_
;
111 float aspect_ratio_goodness_
;
112 ScreenResolution resolution_
;
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) {
125 ResizingHostObserver::~ResizingHostObserver() {
126 if (!original_resolution_
.IsEmpty())
127 desktop_resizer_
->RestoreResolution(original_resolution_
);
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();
136 if (resolution
.IsEmpty())
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
;
146 if (now
< next_allowed_resize
) {
147 deferred_resize_timer_
.Start(
149 next_allowed_resize
- now
,
150 base::Bind(&ResizingHostObserver::SetScreenResolution
,
151 weak_factory_
.GetWeakPtr(), resolution
));
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())
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
;
169 ScreenResolution current_resolution
=
170 desktop_resizer_
->GetCurrentResolution();
171 if (!best_candidate
.resolution().Equals(current_resolution
))
172 desktop_resizer_
->SetResolution(best_candidate
.resolution());
174 // Update the time of last resize to allow it to be rate-limited.
175 previous_resize_time_
= now
;
178 void ResizingHostObserver::SetNowFunctionForTesting(
179 const base::Callback
<base::Time(void)>& now_function
) {
180 now_function_
= now_function
;
183 } // namespace remoting