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 now_function_(base::Bind(base::Time::Now
)),
121 weak_factory_(this) {
124 ResizingHostObserver::~ResizingHostObserver() {
125 if (!original_resolution_
.IsEmpty())
126 desktop_resizer_
->RestoreResolution(original_resolution_
);
129 void 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();
135 if (resolution
.IsEmpty())
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
;
145 if (now
< next_allowed_resize
) {
146 deferred_resize_timer_
.Start(
148 next_allowed_resize
- now
,
149 base::Bind(&ResizingHostObserver::SetScreenResolution
,
150 weak_factory_
.GetWeakPtr(), resolution
));
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())
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
;
168 ScreenResolution current_resolution
=
169 desktop_resizer_
->GetCurrentResolution();
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());
177 // Update the time of last resize to allow it to be rate-limited.
178 previous_resize_time_
= now
;
181 void ResizingHostObserver::SetNowFunctionForTesting(
182 const base::Callback
<base::Time(void)>& now_function
) {
183 now_function_
= now_function
;
186 } // namespace remoting