summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-26 02:02:18 +0000
committerjamiewalch@chromium.org <jamiewalch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-26 02:02:18 +0000
commit05a6071c95d34de491ad22836b1b450e79c0d416 (patch)
tree2ffb8a5bb80db4f2fc022a47c8f00ea8fb09db67
parent6c2432f4a60bb8d6858e3db349097335482660ef (diff)
downloadchromium_src-05a6071c95d34de491ad22836b1b450e79c0d416.zip
chromium_src-05a6071c95d34de491ad22836b1b450e79c0d416.tar.gz
chromium_src-05a6071c95d34de491ad22836b1b450e79c0d416.tar.bz2
Cross-platform plumbing for resize-to-client and Linux implementation.
BUG=145049 Review URL: https://chromiumcodereview.appspot.com/10918224 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158718 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--remoting/host/chromoting_host.cc7
-rw-r--r--remoting/host/chromoting_host.h3
-rw-r--r--remoting/host/client_session.cc3
-rw-r--r--remoting/host/client_session.h6
-rw-r--r--remoting/host/desktop_resizer.h52
-rw-r--r--remoting/host/desktop_resizer_linux.cc45
-rw-r--r--remoting/host/desktop_resizer_mac.cc45
-rw-r--r--remoting/host/desktop_resizer_win.cc45
-rw-r--r--remoting/host/host_mock_objects.h2
-rw-r--r--remoting/host/host_status_observer.h6
-rw-r--r--remoting/host/remoting_me2me_host.cc9
-rw-r--r--remoting/host/resizing_host_observer.cc159
-rw-r--r--remoting/host/resizing_host_observer.h48
-rw-r--r--remoting/host/resizing_host_observer_unittest.cc194
-rw-r--r--remoting/remoting.gyp7
15 files changed, 630 insertions, 1 deletions
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc
index 487ac35..2054eb8 100644
--- a/remoting/host/chromoting_host.cc
+++ b/remoting/host/chromoting_host.cc
@@ -256,6 +256,13 @@ void ChromotingHost::OnSessionRouteChange(
route));
}
+void ChromotingHost::OnClientDimensionsChanged(ClientSession* session,
+ const SkISize& size) {
+ DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
+ FOR_EACH_OBSERVER(HostStatusObserver, status_observers_,
+ OnClientDimensionsChanged(session->client_jid(), size));
+}
+
void ChromotingHost::OnSessionManagerReady() {
DCHECK(context_->network_task_runner()->BelongsToCurrentThread());
// Don't need to do anything here, just wait for incoming
diff --git a/remoting/host/chromoting_host.h b/remoting/host/chromoting_host.h
index a700c24..4742f94 100644
--- a/remoting/host/chromoting_host.h
+++ b/remoting/host/chromoting_host.h
@@ -20,6 +20,7 @@
#include "remoting/protocol/authenticator.h"
#include "remoting/protocol/session_manager.h"
#include "remoting/protocol/connection_to_client.h"
+#include "third_party/skia/include/core/SkSize.h"
namespace remoting {
@@ -115,6 +116,8 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>,
ClientSession* session,
const std::string& channel_name,
const protocol::TransportRoute& route) OVERRIDE;
+ virtual void OnClientDimensionsChanged(ClientSession* session,
+ const SkISize& size) OVERRIDE;
// SessionManager::Listener implementation.
virtual void OnSessionManagerReady() OVERRIDE;
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index a7f89ea..8a9b842 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -70,10 +70,11 @@ ClientSession::ClientSession(
void ClientSession::NotifyClientDimensions(
const protocol::ClientDimensions& dimensions) {
- // TODO(wez): Use the dimensions, e.g. to resize the host desktop to match.
if (dimensions.has_width() && dimensions.has_height()) {
VLOG(1) << "Received ClientDimensions (width="
<< dimensions.width() << ", height=" << dimensions.height() << ")";
+ event_handler_->OnClientDimensionsChanged(
+ this, SkISize::Make(dimensions.width(), dimensions.height()));
}
}
diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h
index af78877..f0ab673 100644
--- a/remoting/host/client_session.h
+++ b/remoting/host/client_session.h
@@ -23,6 +23,7 @@
#include "remoting/protocol/input_filter.h"
#include "remoting/protocol/input_stub.h"
#include "third_party/skia/include/core/SkPoint.h"
+#include "third_party/skia/include/core/SkSize.h"
namespace base {
class SingleThreadTaskRunner;
@@ -75,6 +76,11 @@ class ClientSession
const std::string& channel_name,
const protocol::TransportRoute& route) = 0;
+ // Called when the initial client dimensions are received, and when they
+ // change.
+ virtual void OnClientDimensionsChanged(ClientSession* client,
+ const SkISize& size) = 0;
+
protected:
virtual ~EventHandler() {}
};
diff --git a/remoting/host/desktop_resizer.h b/remoting/host/desktop_resizer.h
new file mode 100644
index 0000000..6ab6f04
--- /dev/null
+++ b/remoting/host/desktop_resizer.h
@@ -0,0 +1,52 @@
+// 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.
+
+#ifndef REMOTING_HOST_DESKTOP_RESIZER_H_
+#define REMOTING_HOST_DESKTOP_RESIZER_H_
+
+#include <list>
+
+#include "base/memory/scoped_ptr.h"
+#include "third_party/skia/include/core/SkRect.h"
+
+namespace remoting {
+
+class DesktopResizer {
+ public:
+ virtual ~DesktopResizer() {}
+
+ // Create a platform-specific DesktopResizer instance.
+ static scoped_ptr<DesktopResizer> Create();
+
+ // Return the current size of the desktop. If resize is not supported, the
+ // implementation should return 0x0, in which case none of the other API
+ // methods will be called.
+ virtual SkISize GetCurrentSize() = 0;
+
+ // Get the list of supported sizes, which should ideally include |preferred|.
+ // Implementations will generally do one of the following:
+ // 1. Return the list of sizes supported by the underlying video driver,
+ // regardless of |preferred|.
+ // 2. Return a list containing just |preferred|, perhaps after imposing
+ // some minimum size constraint. This will typically be the case if
+ // there are no constraints imposed by the underlying video driver.
+ // 3. Return an empty list if resize is not supported.
+ virtual std::list<SkISize> GetSupportedSizes(const SkISize& preferred) = 0;
+
+ // Set the size of the desktop. |size| must be one of the sizes previously
+ // returned by |GetSupportedSizes|. Note that implementations should fail
+ // gracefully if the specified size is no longer supported, since monitor
+ // configurations may change on the fly.
+ virtual void SetSize(const SkISize& size) = 0;
+
+ // Restore the original desktop size. The caller must provide the original
+ // size of the desktop, as returned by |GetCurrentSize|, as a hint. However,
+ // implementaions are free to ignore this. For example, virtual hosts will
+ // typically ignore it to avoid unnecessary resize operations.
+ virtual void RestoreSize(const SkISize& original) = 0;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_DESKTOP_RESIZER_H_
diff --git a/remoting/host/desktop_resizer_linux.cc b/remoting/host/desktop_resizer_linux.cc
new file mode 100644
index 0000000..66fe413
--- /dev/null
+++ b/remoting/host/desktop_resizer_linux.cc
@@ -0,0 +1,45 @@
+// 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/desktop_resizer.h"
+
+#include "base/logging.h"
+
+namespace remoting {
+
+namespace {
+class DesktopResizerLinux : public DesktopResizer {
+ public:
+ DesktopResizerLinux() {
+ }
+
+ virtual SkISize GetCurrentSize() OVERRIDE {
+ NOTIMPLEMENTED();
+ return SkISize::Make(0, 0);
+ }
+
+ virtual std::list<SkISize> GetSupportedSizes(
+ const SkISize& preferred) OVERRIDE {
+ NOTIMPLEMENTED();
+ return std::list<SkISize>();
+ }
+
+ virtual void SetSize(const SkISize& size) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void RestoreSize(const SkISize& original) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopResizerLinux);
+};
+} // namespace
+
+scoped_ptr<DesktopResizer> DesktopResizer::Create() {
+ return scoped_ptr<DesktopResizer>(new DesktopResizerLinux);
+}
+
+} // namespace remoting
diff --git a/remoting/host/desktop_resizer_mac.cc b/remoting/host/desktop_resizer_mac.cc
new file mode 100644
index 0000000..5d23ec6
--- /dev/null
+++ b/remoting/host/desktop_resizer_mac.cc
@@ -0,0 +1,45 @@
+// 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/desktop_resizer.h"
+
+#include "base/logging.h"
+
+namespace remoting {
+
+namespace {
+class DesktopResizerMac : public DesktopResizer {
+ public:
+ DesktopResizerMac() {
+ }
+
+ virtual SkISize GetCurrentSize() OVERRIDE {
+ NOTIMPLEMENTED();
+ return SkISize::Make(0, 0);
+ }
+
+ virtual std::list<SkISize> GetSupportedSizes(
+ const SkISize& preferred) OVERRIDE {
+ NOTIMPLEMENTED();
+ return std::list<SkISize>();
+ }
+
+ virtual void SetSize(const SkISize& size) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void RestoreSize(const SkISize& original) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopResizerMac);
+};
+} // namespace
+
+scoped_ptr<DesktopResizer> DesktopResizer::Create() {
+ return scoped_ptr<DesktopResizer>(new DesktopResizerMac);
+}
+
+} // namespace remoting
diff --git a/remoting/host/desktop_resizer_win.cc b/remoting/host/desktop_resizer_win.cc
new file mode 100644
index 0000000..65ae55a
--- /dev/null
+++ b/remoting/host/desktop_resizer_win.cc
@@ -0,0 +1,45 @@
+// 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/desktop_resizer.h"
+
+#include "base/logging.h"
+
+namespace remoting {
+
+namespace {
+class DesktopResizerWin : public DesktopResizer {
+ public:
+ DesktopResizerWin() {
+ }
+
+ virtual SkISize GetCurrentSize() OVERRIDE {
+ NOTIMPLEMENTED();
+ return SkISize::Make(0, 0);
+ }
+
+ virtual std::list<SkISize> GetSupportedSizes(
+ const SkISize& preferred) OVERRIDE {
+ NOTIMPLEMENTED();
+ return std::list<SkISize>();
+ }
+
+ virtual void SetSize(const SkISize& size) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void RestoreSize(const SkISize& original) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DesktopResizerWin);
+};
+} // namespace
+
+scoped_ptr<DesktopResizer> DesktopResizer::Create() {
+ return scoped_ptr<DesktopResizer>(new DesktopResizerWin);
+}
+
+} // namespace remoting
diff --git a/remoting/host/host_mock_objects.h b/remoting/host/host_mock_objects.h
index 19c05bb..e87c94d 100644
--- a/remoting/host/host_mock_objects.h
+++ b/remoting/host/host_mock_objects.h
@@ -112,6 +112,8 @@ class MockClientSessionEventHandler : public ClientSession::EventHandler {
ClientSession* client,
const std::string& channel_name,
const protocol::TransportRoute& route));
+ MOCK_METHOD2(OnClientDimensionsChanged, void(ClientSession* client,
+ const SkISize& size));
private:
DISALLOW_COPY_AND_ASSIGN(MockClientSessionEventHandler);
diff --git a/remoting/host/host_status_observer.h b/remoting/host/host_status_observer.h
index 200b7d8..a37e9e4 100644
--- a/remoting/host/host_status_observer.h
+++ b/remoting/host/host_status_observer.h
@@ -7,6 +7,8 @@
#include <string>
+#include "third_party/skia/include/core/SkRect.h"
+
namespace net {
class IPEndPoint;
} // namespace net
@@ -44,6 +46,10 @@ class HostStatusObserver {
const std::string& channel_name,
const protocol::TransportRoute& route) {}
+ // Called when the client view dimensions change.
+ virtual void OnClientDimensionsChanged(const std::string& jid,
+ const SkISize& size) {}
+
// Called when hosting is started for an account.
virtual void OnStart(const std::string& xmpp_login) {}
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 911124d..6317b24 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -40,6 +40,7 @@
#include "remoting/host/constants.h"
#include "remoting/host/config_file_watcher.h"
#include "remoting/host/desktop_environment_factory.h"
+#include "remoting/host/desktop_resizer.h"
#include "remoting/host/dns_blackhole_checker.h"
#include "remoting/host/event_executor.h"
#include "remoting/host/heartbeat_sender.h"
@@ -50,6 +51,7 @@
#include "remoting/host/log_to_server.h"
#include "remoting/host/network_settings.h"
#include "remoting/host/policy_hack/policy_watcher.h"
+#include "remoting/host/resizing_host_observer.h"
#include "remoting/host/session_manager_factory.h"
#include "remoting/host/signaling_connector.h"
#include "remoting/host/usage_stats_consent.h"
@@ -130,6 +132,7 @@ class HostProcess
#else // !defined(OS_WIN)
desktop_environment_factory_(new DesktopEnvironmentFactory()),
#endif // !defined(OS_WIN)
+ desktop_resizer_(DesktopResizer::Create()),
exit_code_(kSuccessExitCode)
#if defined(OS_MACOSX)
, curtain_(base::Bind(&HostProcess::OnDisconnectRequested,
@@ -583,6 +586,9 @@ class HostProcess
new LogToServer(host_, ServerLogEntry::ME2ME, signal_strategy_.get()));
host_event_logger_ = HostEventLogger::Create(host_, kApplicationName);
+ resizing_host_observer_.reset(
+ new ResizingHostObserver(desktop_resizer_.get(), host_));
+
#if defined(OS_MACOSX) || defined(OS_WIN)
if (host_user_interface_.get()) {
host_user_interface_->Start(
@@ -675,6 +681,7 @@ class HostProcess
heartbeat_sender_.reset();
signaling_connector_.reset();
signal_strategy_.reset();
+ resizing_host_observer_.reset();
}
scoped_ptr<ChromotingHostContext> context_;
@@ -705,6 +712,8 @@ class HostProcess
bool shutting_down_;
scoped_ptr<DesktopEnvironmentFactory> desktop_environment_factory_;
+ scoped_ptr<DesktopResizer> desktop_resizer_;
+ scoped_ptr<ResizingHostObserver> resizing_host_observer_;
scoped_ptr<XmppSignalStrategy> signal_strategy_;
scoped_ptr<SignalingConnector> signaling_connector_;
scoped_ptr<HeartbeatSender> heartbeat_sender_;
diff --git a/remoting/host/resizing_host_observer.cc b/remoting/host/resizing_host_observer.cc
new file mode 100644
index 0000000..ba419512
--- /dev/null
+++ b/remoting/host/resizing_host_observer.cc
@@ -0,0 +1,159 @@
+// 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 "remoting/host/desktop_resizer.h"
+
+#include <set>
+
+#include "base/logging.h"
+
+namespace remoting {
+
+ResizingHostObserver::ResizingHostObserver(
+ DesktopResizer* desktop_resizer, ChromotingHost* host)
+ : desktop_resizer_(desktop_resizer),
+ host_(host),
+ original_size_(SkISize::Make(0, 0)),
+ previous_size_(SkISize::Make(0, 0)) {
+ if (host_) {
+ host_->AddStatusObserver(this);
+ }
+}
+
+ResizingHostObserver::~ResizingHostObserver() {
+ if (host_) {
+ host_->RemoveStatusObserver(this);
+ }
+}
+
+void ResizingHostObserver::OnClientAuthenticated(const std::string& jid) {
+ // This implementation assumes a single connected client, which is what the
+ // host currently supports
+ DCHECK(client_jid_.empty());
+ original_size_ = desktop_resizer_->GetCurrentSize();
+ previous_size_ = original_size_;
+}
+
+void ResizingHostObserver::OnClientDisconnected(const std::string& jid) {
+ if (!original_size_.isZero()) {
+ desktop_resizer_->RestoreSize(original_size_);
+ original_size_.set(0, 0);
+ }
+ client_jid_.clear();
+}
+
+class CandidateSize {
+ public:
+ CandidateSize(const SkISize& candidate, const SkISize& preferred)
+ : size_(candidate) {
+ // The client scale factor is the smaller of the candidate:preferred ratios
+ // for width and height.
+ if ((candidate.width() > preferred.width()) ||
+ (candidate.height() > preferred.height())) {
+ float ratio_width =
+ static_cast<float>(preferred.width()) / candidate.width();
+ float ratio_height =
+ static_cast<float>(preferred.height()) / candidate.height();
+ client_scale_factor_ = std::min(ratio_width, ratio_height);
+ } 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 the differ in opposite
+ // directions to be compared numerically.
+ float candidate_aspect_ratio =
+ static_cast<float>(candidate.width()) / candidate.height();
+ float preferred_aspect_ratio =
+ static_cast<float>(preferred.width()) / preferred.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 SkISize& size() const { return size_; }
+ float client_scale_factor() const { return client_scale_factor_; }
+ float aspect_ratio_goodness() const { return aspect_ratio_goodness_; }
+ int64 area() const {
+ return static_cast<int64>(size_.width()) * size_.height();
+ }
+
+ bool IsBetterThan(const CandidateSize& other) const {
+ // If either size 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 sizes 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 size with the largest area.
+ if (area() < other.area()) {
+ return false;
+ } else if (area() > other.area()) {
+ return true;
+ }
+
+ // If the areas are equal, pick the size 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;
+ }
+
+ // If the aspect ratios are equally good (for example, comparing 640x480
+ // to 480x640 w.r.t. 640x640), just pick the widest, since desktop UIs
+ // are typically design for landscape aspect ratios.
+ return size().width() > other.size().width();
+ }
+
+ private:
+ float client_scale_factor_;
+ float aspect_ratio_goodness_;
+ SkISize size_;
+};
+
+void ResizingHostObserver::OnClientDimensionsChanged(
+ const std::string& jid, const SkISize& preferred_size) {
+ if (previous_size_.isZero()) {
+ return;
+ }
+
+ // If the host desktop size changes other than via the resize-to-client
+ // mechanism, then set |previous_size_| to zero and give up. This is an
+ // indication that the user doesn't want resize-to-client.
+ SkISize new_size = desktop_resizer_->GetCurrentSize();
+ if (new_size != previous_size_) {
+ previous_size_ = SkISize::Make(0, 0);
+ return;
+ }
+
+ // If the implementation returns any sizes, pick the best one according to
+ // the algorithm described in CandidateSize::IsBetterThen.
+ std::list<SkISize> sizes =
+ desktop_resizer_->GetSupportedSizes(preferred_size);
+ if (sizes.empty()) {
+ return;
+ }
+ CandidateSize best_size(sizes.front(), preferred_size);
+ std::list<SkISize>::iterator i = sizes.begin();
+ for (++i; i != sizes.end(); ++i) {
+ CandidateSize candidate_size(*i, preferred_size);
+ if (candidate_size.IsBetterThan(best_size)) {
+ best_size = candidate_size;
+ }
+ }
+ previous_size_ = best_size.size();
+ desktop_resizer_->SetSize(previous_size_);
+}
+
+} // namespace remoting
diff --git a/remoting/host/resizing_host_observer.h b/remoting/host/resizing_host_observer.h
new file mode 100644
index 0000000..cdafa89
--- /dev/null
+++ b/remoting/host/resizing_host_observer.h
@@ -0,0 +1,48 @@
+// 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.
+
+#ifndef REMOTING_HOST_RESIZING_HOST_OBSERVER_H_
+#define REMOTING_HOST_RESIZING_HOST_OBSERVER_H_
+
+#include <string>
+
+#include "remoting/host/host_status_observer.h"
+#include "remoting/host/chromoting_host.h"
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "third_party/skia/include/core/SkSize.h"
+
+namespace remoting {
+
+class DesktopResizer;
+
+// Use the specified DesktopResizer to match host desktop size to the client
+// view size as closely as is possible. When the connection closes, restore
+// the original desktop size.
+class ResizingHostObserver : public HostStatusObserver {
+ public:
+ explicit ResizingHostObserver(DesktopResizer* desktop_resizer,
+ ChromotingHost* host);
+ virtual ~ResizingHostObserver();
+
+ // HostStatusObserver interface
+ virtual void OnClientAuthenticated(const std::string& jid) OVERRIDE;
+ virtual void OnClientDisconnected(const std::string& jid) OVERRIDE;
+ virtual void OnClientDimensionsChanged(const std::string& jid,
+ const SkISize& size) OVERRIDE;
+
+ private:
+ DesktopResizer* desktop_resizer_;
+ scoped_refptr<ChromotingHost> host_;
+ SkISize original_size_;
+ SkISize previous_size_;
+ std::string client_jid_;
+
+ DISALLOW_COPY_AND_ASSIGN(ResizingHostObserver);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_RESIZING_HOST_OBSERVER_H_
diff --git a/remoting/host/resizing_host_observer_unittest.cc b/remoting/host/resizing_host_observer_unittest.cc
new file mode 100644
index 0000000..4c5fc3b
--- /dev/null
+++ b/remoting/host/resizing_host_observer_unittest.cc
@@ -0,0 +1,194 @@
+// 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 "remoting/host/desktop_resizer.h"
+
+#include <list>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkSize.h"
+
+std::ostream& operator<<(std::ostream& os, const SkISize& size) {
+ return os << size.width() << "x" << size.height();
+}
+
+namespace remoting {
+
+class MockDesktopResizer : public DesktopResizer {
+ public:
+ MockDesktopResizer(const SkISize& initial_size, bool exact_size_supported,
+ const SkISize* supported_sizes, int num_supported_sizes)
+ : initial_size_(initial_size),
+ current_size_(initial_size),
+ exact_size_supported_(exact_size_supported) {
+ for (int i = 0; i < num_supported_sizes; ++i) {
+ supported_sizes_.push_back(supported_sizes[i]);
+ }
+ }
+
+ const SkISize& initial_size() { return initial_size_; }
+
+ // remoting::DesktopResizer interface
+ virtual SkISize GetCurrentSize() OVERRIDE {
+ return current_size_;
+ }
+ virtual std::list<SkISize> GetSupportedSizes(
+ const SkISize& preferred) OVERRIDE {
+ std::list<SkISize> result = supported_sizes_;
+ if (exact_size_supported_) {
+ result.push_back(preferred);
+ }
+ return result;
+ }
+ virtual void SetSize(const SkISize& size) OVERRIDE {
+ current_size_ = size;
+ }
+ virtual void RestoreSize(const SkISize& size) OVERRIDE {
+ current_size_ = size;
+ }
+
+ private:
+ SkISize initial_size_;
+ SkISize current_size_;
+ bool exact_size_supported_;
+ std::list<SkISize> supported_sizes_;
+};
+
+class ResizingHostObserverTest : public testing::Test {
+ public:
+ void SetDesktopResizer(MockDesktopResizer* desktop_resizer) {
+ CHECK(!desktop_resizer_.get()) << "Call SetDeskopResizer once per test";
+ resizing_host_observer_.reset(new ResizingHostObserver(desktop_resizer,
+ NULL));
+ desktop_resizer_.reset(desktop_resizer);
+ resizing_host_observer_->OnClientAuthenticated("");
+ }
+
+ SkISize GetBestSize(const SkISize& client_size) {
+ resizing_host_observer_->OnClientDimensionsChanged("", client_size);
+ return desktop_resizer_->GetCurrentSize();
+ }
+
+ void VerifySizes(const SkISize* client_sizes, const SkISize* expected_sizes,
+ int number_of_sizes) {
+ for (int i = 0; i < number_of_sizes; ++i) {
+ SkISize best_size = GetBestSize(client_sizes[i]);
+ EXPECT_EQ(expected_sizes[i], best_size)
+ << "Input size = " << client_sizes[i];
+ }
+ }
+
+ void Reconnect() {
+ resizing_host_observer_->OnClientDisconnected("");
+ resizing_host_observer_->OnClientAuthenticated("");
+ }
+
+ // testing::Test interface
+ virtual void TearDown() OVERRIDE {
+ resizing_host_observer_->OnClientDisconnected("");
+ EXPECT_EQ(desktop_resizer_->initial_size(),
+ desktop_resizer_->GetCurrentSize());
+ }
+
+ private:
+ scoped_ptr<ResizingHostObserver> resizing_host_observer_;
+ scoped_ptr<MockDesktopResizer> desktop_resizer_;
+};
+
+// Check that the host is not resized if it reports an initial size of zero
+// (even if it GetSupportedSizes does not return an empty list).
+TEST_F(ResizingHostObserverTest, ZeroGetCurrentSize) {
+ SkISize zero = { 0, 0 };
+ SetDesktopResizer(
+ new MockDesktopResizer(zero, true, NULL, 0));
+ SkISize client_sizes[] = { { 200, 100 }, { 100, 200 } };
+ SkISize expected_sizes[] = { zero, zero };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+}
+
+// Check that the host is not resized if GetSupportedSizes returns an empty
+// list (even if GetCurrentSize is supported).
+TEST_F(ResizingHostObserverTest, EmptyGetSupportedSizes) {
+ SkISize initial = { 640, 480 };
+ SetDesktopResizer(
+ new MockDesktopResizer(initial, false, NULL, 0));
+ SkISize client_sizes[] = { { 200, 100 }, { 100, 200 } };
+ SkISize expected_sizes[] = { initial, initial };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+}
+
+// Check that if the implementation supports exact size matching, it is used.
+TEST_F(ResizingHostObserverTest, SelectExactSize) {
+ SetDesktopResizer(
+ new MockDesktopResizer(SkISize::Make(640, 480), true, NULL, 0));
+ SkISize client_sizes[] = { { 200, 100 }, { 100, 200 } , { 640, 480 },
+ { 480, 640 }, { 1280, 1024 } };
+ VerifySizes(client_sizes, client_sizes, arraysize(client_sizes));
+}
+
+// Check that if the implementation supports a size that is no larger than
+// the requested size, then the largest such size is used.
+TEST_F(ResizingHostObserverTest, SelectBestSmallerSize) {
+ SkISize supported_sizes[] = {
+ SkISize::Make(639, 479), SkISize::Make(640, 480) };
+ SetDesktopResizer(
+ new MockDesktopResizer(SkISize::Make(640, 480), false,
+ supported_sizes, arraysize(supported_sizes)));
+ SkISize client_sizes[] = { { 639, 479 }, { 640, 480 }, { 641, 481 },
+ { 999, 999 } };
+ SkISize expected_sizes[] = { supported_sizes[0], supported_sizes[1],
+ supported_sizes[1], supported_sizes[1] };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+}
+
+// Check that if the implementation supports only sizes that are larger than
+// the requested size, then the one that requires the least down-scaling.
+TEST_F(ResizingHostObserverTest, SelectBestScaleFactor) {
+ SkISize supported_sizes[] = {
+ SkISize::Make(100, 100), SkISize::Make(200, 100) };
+ SetDesktopResizer(
+ new MockDesktopResizer(SkISize::Make(200, 100), false,
+ supported_sizes, arraysize(supported_sizes)));
+ SkISize client_sizes[] = { { 1, 1 }, { 99, 99 }, { 199, 99 } };
+ SkISize expected_sizes[] = { supported_sizes[0], supported_sizes[0],
+ supported_sizes[1] };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+}
+
+// Check that if the implementation supports two sizes that have the same
+// resultant scale factor, then the widest one is selected.
+TEST_F(ResizingHostObserverTest, SelectWidest) {
+ SkISize supported_sizes[] = {
+ SkISize::Make(640, 480), SkISize::Make(480, 640) };
+ SetDesktopResizer(
+ new MockDesktopResizer(SkISize::Make(480, 640), false,
+ supported_sizes, arraysize(supported_sizes)));
+ SkISize client_sizes[] = { { 100, 100 }, { 480, 480 }, { 500, 500 },
+ { 640, 640 }, { 1000, 1000 } };
+ SkISize expected_sizes[] = { supported_sizes[0], supported_sizes[0],
+ supported_sizes[0], supported_sizes[0],
+ supported_sizes[0] };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+}
+
+// Check that resize-to-client is disabled if the size is changed explicitly.
+TEST_F(ResizingHostObserverTest, ManualResize) {
+ MockDesktopResizer* desktop_resizer =
+ new MockDesktopResizer(SkISize::Make(640, 480), true, NULL, 0);
+ SetDesktopResizer(desktop_resizer);
+ SkISize client_sizes[] = { { 1, 1 }, { 2, 2 } , { 3, 3 } };
+ VerifySizes(client_sizes, client_sizes, arraysize(client_sizes));
+ SkISize explicit_size = SkISize::Make(640, 480);
+ desktop_resizer->SetSize(explicit_size);
+ SkISize expected_sizes[] = { explicit_size, explicit_size, explicit_size };
+ VerifySizes(client_sizes, expected_sizes, arraysize(client_sizes));
+ // Make sure this behaviour doesn't persist across reconnect.
+ Reconnect();
+ VerifySizes(client_sizes, client_sizes, arraysize(client_sizes));
+}
+
+} // namespace remoting
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 9f01d58..3fc9245 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -1355,6 +1355,10 @@
'host/desktop_environment.h',
'host/desktop_environment_factory.cc',
'host/desktop_environment_factory.h',
+ 'host/desktop_resizer.h',
+ 'host/desktop_resizer_linux.cc',
+ 'host/desktop_resizer_win.cc',
+ 'host/desktop_resizer_mac.cc',
'host/differ.cc',
'host/differ.h',
'host/disconnect_window.h',
@@ -1416,6 +1420,8 @@
'host/register_support_host_request.h',
'host/remote_input_filter.cc',
'host/remote_input_filter.h',
+ 'host/resizing_host_observer.cc',
+ 'host/resizing_host_observer.h',
'host/sas_injector.h',
'host/sas_injector_win.cc',
'host/screen_recorder.cc',
@@ -1915,6 +1921,7 @@
'host/policy_hack/policy_watcher_unittest.cc',
'host/register_support_host_request_unittest.cc',
'host/remote_input_filter_unittest.cc',
+ 'host/resizing_host_observer_unittest.cc',
'host/screen_recorder_unittest.cc',
'host/server_log_entry_unittest.cc',
'host/test_key_pair.h',