// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/host/resizing_host_observer.h" #include #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "remoting/host/desktop_resizer.h" #include "remoting/host/screen_resolution.h" namespace remoting { namespace { // Minimum amount of time to wait between desktop resizes. Note that this // constant is duplicated by the ResizingHostObserverTest.RateLimited // unit-test and must be kept in sync. const int kMinimumResizeIntervalMs = 1000; class CandidateResolution { public: CandidateResolution(const ScreenResolution& candidate, const ScreenResolution& preferred) : resolution_(candidate) { // Protect against division by zero. CHECK(!candidate.IsEmpty()); DCHECK(!preferred.IsEmpty()); // The client scale factor is the smaller of the candidate:preferred ratios // for width and height. if ((candidate.dimensions().width() > preferred.dimensions().width()) || (candidate.dimensions().height() > preferred.dimensions().height())) { const float width_ratio = static_cast(preferred.dimensions().width()) / candidate.dimensions().width(); const float height_ratio = static_cast(preferred.dimensions().height()) / candidate.dimensions().height(); client_scale_factor_ = std::min(width_ratio, height_ratio); } else { // Since clients do not scale up, 1.0 is the maximum. client_scale_factor_ = 1.0; } // The aspect ratio "goodness" is defined as being the ratio of the smaller // of the two aspect ratios (candidate and preferred) to the larger. The // best aspect ratio is the one that most closely matches the preferred // aspect ratio (in other words, the ideal aspect ratio "goodness" is 1.0). // By keeping the values < 1.0, it allows ratios that differ in opposite // directions to be compared numerically. float candidate_aspect_ratio = static_cast(candidate.dimensions().width()) / candidate.dimensions().height(); float preferred_aspect_ratio = static_cast(preferred.dimensions().width()) / preferred.dimensions().height(); if (candidate_aspect_ratio > preferred_aspect_ratio) { aspect_ratio_goodness_ = preferred_aspect_ratio / candidate_aspect_ratio; } else { aspect_ratio_goodness_ = candidate_aspect_ratio / preferred_aspect_ratio; } } const ScreenResolution& resolution() const { return resolution_; } float client_scale_factor() const { return client_scale_factor_; } float aspect_ratio_goodness() const { return aspect_ratio_goodness_; } int64 area() const { return static_cast(resolution_.dimensions().width()) * resolution_.dimensions().height(); } // TODO(jamiewalch): Also compare the DPI: http://crbug.com/172405 bool IsBetterThan(const CandidateResolution& other) const { // If either resolution would require down-scaling, prefer the one that // down-scales the least (since the client scale factor is at most 1.0, // this does not differentiate between resolutions that don't require // down-scaling). if (client_scale_factor() < other.client_scale_factor()) { return false; } else if (client_scale_factor() > other.client_scale_factor()) { return true; } // If the scale factors are the same, pick the resolution with the largest // area. if (area() < other.area()) { return false; } else if (area() > other.area()) { return true; } // If the areas are equal, pick the resolution with the "best" aspect ratio. if (aspect_ratio_goodness() < other.aspect_ratio_goodness()) { return false; } else if (aspect_ratio_goodness() > other.aspect_ratio_goodness()) { return true; } // All else being equal (for example, comparing 640x480 to 480x640 w.r.t. // 640x640), just pick the widest, since desktop UIs are typically designed // for landscape aspect ratios. return resolution().dimensions().width() > other.resolution().dimensions().width(); } private: float client_scale_factor_; float aspect_ratio_goodness_; ScreenResolution resolution_; }; } // namespace ResizingHostObserver::ResizingHostObserver( scoped_ptr desktop_resizer) : desktop_resizer_(desktop_resizer.Pass()), now_function_(base::Bind(base::Time::Now)), weak_factory_(this) { } ResizingHostObserver::~ResizingHostObserver() { if (!original_resolution_.IsEmpty()) desktop_resizer_->RestoreResolution(original_resolution_); } void ResizingHostObserver::SetScreenResolution( const ScreenResolution& resolution) { // Get the current time. This function is called exactly once for each call // to SetScreenResolution to simplify the implementation of unit-tests. base::Time now = now_function_.Run(); if (resolution.IsEmpty()) return; // Resizing the desktop too often is probably not a good idea, so apply a // simple rate-limiting scheme. base::TimeDelta minimum_resize_interval = base::TimeDelta::FromMilliseconds(kMinimumResizeIntervalMs); base::Time next_allowed_resize = previous_resize_time_ + minimum_resize_interval; if (now < next_allowed_resize) { deferred_resize_timer_.Start( FROM_HERE, next_allowed_resize - now, base::Bind(&ResizingHostObserver::SetScreenResolution, weak_factory_.GetWeakPtr(), resolution)); return; } // If the implementation returns any resolutions, pick the best one according // to the algorithm described in CandidateResolution::IsBetterThen. std::list resolutions = desktop_resizer_->GetSupportedResolutions(resolution); if (resolutions.empty()) return; CandidateResolution best_candidate(resolutions.front(), resolution); for (std::list::const_iterator i = ++resolutions.begin(); i != resolutions.end(); ++i) { CandidateResolution candidate(*i, resolution); if (candidate.IsBetterThan(best_candidate)) { best_candidate = candidate; } } ScreenResolution current_resolution = desktop_resizer_->GetCurrentResolution(); if (!best_candidate.resolution().Equals(current_resolution)) { if (original_resolution_.IsEmpty()) original_resolution_ = current_resolution; desktop_resizer_->SetResolution(best_candidate.resolution()); } // Update the time of last resize to allow it to be rate-limited. previous_resize_time_ = now; } void ResizingHostObserver::SetNowFunctionForTesting( const base::Callback& now_function) { now_function_ = now_function; } } // namespace remoting