summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--remoting/base/capabilities.cc44
-rw-r--r--remoting/base/capabilities.h24
-rw-r--r--remoting/base/capabilities_unittest.cc106
-rw-r--r--remoting/client/chromoting_client.cc42
-rw-r--r--remoting/client/chromoting_client.h20
-rw-r--r--remoting/client/client_config.h3
-rw-r--r--remoting/client/client_user_interface.h7
-rw-r--r--remoting/client/plugin/chromoting_instance.cc23
-rw-r--r--remoting/client/plugin/chromoting_instance.h10
-rw-r--r--remoting/host/basic_desktop_environment.cc7
-rw-r--r--remoting/host/basic_desktop_environment.h4
-rw-r--r--remoting/host/chromoting_host_unittest.cc15
-rw-r--r--remoting/host/client_session.cc65
-rw-r--r--remoting/host/client_session.h10
-rw-r--r--remoting/host/client_session_unittest.cc7
-rw-r--r--remoting/host/desktop_environment.h7
-rw-r--r--remoting/host/desktop_process_unittest.cc15
-rw-r--r--remoting/host/desktop_session_proxy.cc72
-rw-r--r--remoting/host/desktop_session_proxy.h18
-rw-r--r--remoting/host/host_mock_objects.h2
-rw-r--r--remoting/host/ipc_desktop_environment.cc15
-rw-r--r--remoting/host/ipc_desktop_environment.h2
-rw-r--r--remoting/host/ipc_desktop_environment_unittest.cc17
-rw-r--r--remoting/proto/control.proto6
-rw-r--r--remoting/proto/internal.proto1
-rw-r--r--remoting/protocol/client_control_dispatcher.cc9
-rw-r--r--remoting/protocol/client_control_dispatcher.h1
-rw-r--r--remoting/protocol/client_stub.h6
-rw-r--r--remoting/protocol/host_control_dispatcher.cc9
-rw-r--r--remoting/protocol/host_control_dispatcher.h3
-rw-r--r--remoting/protocol/host_stub.h4
-rw-r--r--remoting/protocol/protocol_mock_objects.h10
-rw-r--r--remoting/protocol/session_config.cc17
-rw-r--r--remoting/protocol/session_config.h3
-rw-r--r--remoting/remoting.gyp7
-rw-r--r--remoting/webapp/client_plugin.js2
-rw-r--r--remoting/webapp/client_plugin_async.js63
-rw-r--r--remoting/webapp/client_session.js58
38 files changed, 667 insertions, 67 deletions
diff --git a/remoting/base/capabilities.cc b/remoting/base/capabilities.cc
new file mode 100644
index 0000000..47756d2
--- /dev/null
+++ b/remoting/base/capabilities.cc
@@ -0,0 +1,44 @@
+// Copyright 2013 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/base/capabilities.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/string_util.h"
+
+namespace remoting {
+
+bool HasCapability(const std::string& capabilities, const std::string& key) {
+ std::vector<std::string> caps;
+ Tokenize(capabilities, " ", &caps);
+ return std::find(caps.begin(), caps.end(), key) != caps.end();
+}
+
+std::string IntersectCapabilities(const std::string& client_capabilities,
+ const std::string& host_capabilities) {
+ std::vector<std::string> client_caps;
+ Tokenize(client_capabilities, " ", &client_caps);
+ std::sort(client_caps.begin(), client_caps.end());
+
+ std::vector<std::string> host_caps;
+ Tokenize(host_capabilities, " ", &host_caps);
+ std::sort(host_caps.begin(), host_caps.end());
+
+ std::vector<std::string> result(std::min(client_caps.size(),
+ host_caps.size()));
+ std::vector<std::string>::iterator end =
+ std::set_intersection(client_caps.begin(),
+ client_caps.end(),
+ host_caps.begin(),
+ host_caps.end(),
+ result.begin());
+ if (end != result.end())
+ result.erase(end, result.end());
+
+ return JoinString(result, " ");
+}
+
+} // namespace remoting
diff --git a/remoting/base/capabilities.h b/remoting/base/capabilities.h
new file mode 100644
index 0000000..0a285be
--- /dev/null
+++ b/remoting/base/capabilities.h
@@ -0,0 +1,24 @@
+// Copyright 2013 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_BASE_CAPABILITIES_H_
+#define REMOTING_BASE_CAPABILITIES_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+namespace remoting {
+
+// Returns true if |capabilities| contain capability |key|.
+bool HasCapability(const std::string& capabilities, const std::string& key);
+
+// Returns a set of capabilities contained in both |client_capabilities| and
+// |host_capabilities| sets.
+std::string IntersectCapabilities(const std::string& client_capabilities,
+ const std::string& host_capabilities);
+
+} // namespace remoting
+
+#endif // REMOTING_BASE_CAPABILITIES_H_
diff --git a/remoting/base/capabilities_unittest.cc b/remoting/base/capabilities_unittest.cc
new file mode 100644
index 0000000..16b9c0f
--- /dev/null
+++ b/remoting/base/capabilities_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright 2013 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 <algorithm>
+#include <vector>
+
+#include "base/string_util.h"
+#include "remoting/base/capabilities.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+struct HasCapabilityTestData {
+ const char* capabilities;
+ const char* key;
+ bool result;
+};
+
+struct IntersectTestData {
+ const char* left;
+ const char* right;
+ const char* result;
+};
+
+} // namespace
+
+namespace remoting {
+
+TEST(CapabilitiesTest, Empty) {
+ // Expect that nothing can be found in an empty set.
+ EXPECT_FALSE(HasCapability("", "a"));
+ EXPECT_FALSE(HasCapability(" ", "a"));
+ EXPECT_FALSE(HasCapability(" ", "a"));
+
+ // Expect that nothing can be found in an empty set, event when the key is
+ // empty.
+ EXPECT_FALSE(HasCapability("", ""));
+ EXPECT_FALSE(HasCapability(" ", ""));
+ EXPECT_FALSE(HasCapability(" ", ""));
+}
+
+TEST(CapabilitiesTest, HasCapability) {
+ HasCapabilityTestData data[] = {
+ { "", "", false },
+ { "a", "", false },
+ { "a", "a", true },
+ { "a a", "", false },
+ { "a a", "a", true },
+ { "a a", "z", false },
+ { "a b", "", false },
+ { "a b", "a", true },
+ { "a b", "b", true },
+ { "a b", "z", false },
+ { "a b c", "", false },
+ { "a b c", "a", true },
+ { "a b c", "b", true },
+ { "a b c", "z", false }
+ };
+
+ // Verify that HasCapability(|capabilities|, |key|) returns |result|.
+ // |result|.
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ std::vector<std::string> caps;
+ Tokenize(data[i].capabilities, " ", &caps);
+
+ do {
+ EXPECT_EQ(HasCapability(JoinString(caps, " "), data[i].key),
+ data[i].result);
+ } while (std::next_permutation(caps.begin(), caps.end()));
+ }
+}
+
+TEST(CapabilitiesTest, Intersect) {
+ EXPECT_EQ(IntersectCapabilities("a", "a"), "a");
+
+ IntersectTestData data[] = {
+ { "", "", "" },
+ { "a", "", "" },
+ { "a", "a", "a" },
+ { "a", "b", "" },
+ { "a b", "", "" },
+ { "a b", "a", "a" },
+ { "a b", "b", "b" },
+ { "a b", "z", "" },
+ { "a b c", "a", "a" },
+ { "a b c", "b", "b" },
+ { "a b c", "a b", "a b" },
+ { "a b c", "b a", "a b" },
+ { "a b c", "z", "" }
+ };
+
+ // Verify that intersection of |right| with all permutations of |left| yields
+ // |result|.
+ for (size_t i = 0; i < arraysize(data); ++i) {
+ std::vector<std::string> caps;
+ Tokenize(data[i].left, " ", &caps);
+
+ do {
+ EXPECT_EQ(IntersectCapabilities(JoinString(caps, " "), data[i].right),
+ data[i].result);
+ } while (std::next_permutation(caps.begin(), caps.end()));
+ }
+}
+
+} // namespace remoting
diff --git a/remoting/client/chromoting_client.cc b/remoting/client/chromoting_client.cc
index 28d5154..c9208ff 100644
--- a/remoting/client/chromoting_client.cc
+++ b/remoting/client/chromoting_client.cc
@@ -5,6 +5,7 @@
#include "remoting/client/chromoting_client.h"
#include "base/bind.h"
+#include "remoting/base/capabilities.h"
#include "remoting/client/audio_decode_scheduler.h"
#include "remoting/client/audio_player.h"
#include "remoting/client/client_context.h"
@@ -14,6 +15,7 @@
#include "remoting/proto/video.pb.h"
#include "remoting/protocol/authentication_method.h"
#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/negotiating_client_authenticator.h"
#include "remoting/protocol/session_config.h"
#include "remoting/protocol/transport.h"
@@ -33,6 +35,7 @@ ChromotingClient::ChromotingClient(
task_runner_(client_context->main_task_runner()),
connection_(connection),
user_interface_(user_interface),
+ host_capabilities_received_(false),
weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
rectangle_decoder_ =
new RectangleUpdateDecoder(client_context->main_task_runner(),
@@ -88,14 +91,39 @@ ChromotingStats* ChromotingClient::GetStats() {
return rectangle_decoder_->GetStats();
}
+void ChromotingClient::SetCapabilities(
+ const protocol::Capabilities& capabilities) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
+ // Only accept the first |protocol::Capabilities| message.
+ if (host_capabilities_received_) {
+ LOG(WARNING) << "protocol::Capabilities has been received already.";
+ return;
+ }
+
+ host_capabilities_received_ = true;
+ if (capabilities.has_capabilities())
+ host_capabilities_ = capabilities.capabilities();
+
+ VLOG(1) << "Host capabilities: " << host_capabilities_;
+
+ // Calculate the set of capabilities enabled by both client and host and pass
+ // it to the webapp.
+ user_interface_->SetCapabilities(
+ IntersectCapabilities(config_.capabilities, host_capabilities_));
+}
+
void ChromotingClient::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
DCHECK(task_runner_->BelongsToCurrentThread());
+
user_interface_->GetClipboardStub()->InjectClipboardEvent(event);
}
void ChromotingClient::SetCursorShape(
const protocol::CursorShapeInfo& cursor_shape) {
+ DCHECK(task_runner_->BelongsToCurrentThread());
+
user_interface_->GetCursorShapeStub()->SetCursorShape(cursor_shape);
}
@@ -121,6 +149,20 @@ void ChromotingClient::Initialize() {
rectangle_decoder_->Initialize(connection_->config());
if (connection_->config().is_audio_enabled())
audio_decode_scheduler_->Initialize(connection_->config());
+
+ // Negotiate capabilities with the host.
+ if (connection_->config().SupportsCapabilities()) {
+ VLOG(1) << "Client capabilities: " << config_.capabilities;
+
+ protocol::Capabilities capabilities;
+ capabilities.set_capabilities(config_.capabilities);
+ connection_->host_stub()->SetCapabilities(capabilities);
+ } else {
+ VLOG(1) << "The host does not support any capabilities.";
+
+ host_capabilities_received_ = true;
+ user_interface_->SetCapabilities(host_capabilities_);
+ }
}
} // namespace remoting
diff --git a/remoting/client/chromoting_client.h b/remoting/client/chromoting_client.h
index d6fc09b..a04606e 100644
--- a/remoting/client/chromoting_client.h
+++ b/remoting/client/chromoting_client.h
@@ -7,7 +7,7 @@
#ifndef REMOTING_CLIENT_CHROMOTING_CLIENT_H_
#define REMOTING_CLIENT_CHROMOTING_CLIENT_H_
-#include <list>
+#include <string>
#include "base/callback.h"
#include "base/memory/scoped_ptr.h"
@@ -61,13 +61,17 @@ class ChromotingClient : public protocol::ConnectionToHost::HostEventCallback,
// Return the stats recorded by this client.
ChromotingStats* GetStats();
+ // ClientStub implementation.
+ virtual void SetCapabilities(
+ const protocol::Capabilities& capabilities) OVERRIDE;
+
// ClipboardStub implementation for receiving clipboard data from host.
- virtual void InjectClipboardEvent(const protocol::ClipboardEvent& event)
- OVERRIDE;
+ virtual void InjectClipboardEvent(
+ const protocol::ClipboardEvent& event) OVERRIDE;
// CursorShapeStub implementation for receiving cursor shape updates.
- virtual void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape)
- OVERRIDE;
+ virtual void SetCursorShape(
+ const protocol::CursorShapeInfo& cursor_shape) OVERRIDE;
// ConnectionToHost::HostEventCallback implementation.
virtual void OnConnectionState(
@@ -93,6 +97,12 @@ class ChromotingClient : public protocol::ConnectionToHost::HostEventCallback,
// If non-NULL, this is called when the client is done.
base::Closure client_done_;
+ // The set of all capabilities supported by the host.
+ std::string host_capabilities_;
+
+ // True if |protocol::Capabilities| message has been received.
+ bool host_capabilities_received_;
+
// Record the statistics of the connection.
ChromotingStats stats_;
diff --git a/remoting/client/client_config.h b/remoting/client/client_config.h
index 9b954d1..35f7286 100644
--- a/remoting/client/client_config.h
+++ b/remoting/client/client_config.h
@@ -27,6 +27,9 @@ struct ClientConfig {
std::vector<protocol::AuthenticationMethod> authentication_methods;
std::string authentication_tag;
+
+ // The set of all capabilities supported by the webapp.
+ std::string capabilities;
};
} // namespace remoting
diff --git a/remoting/client/client_user_interface.h b/remoting/client/client_user_interface.h
index 646c7c8..59460f1 100644
--- a/remoting/client/client_user_interface.h
+++ b/remoting/client/client_user_interface.h
@@ -5,7 +5,10 @@
#ifndef REMOTING_CLIENT_CLIENT_USER_INTERFACE_H_
#define REMOTING_CLIENT_CLIENT_USER_INTERFACE_H_
+#include <string>
+
#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/third_party_client_authenticator.h"
@@ -30,6 +33,10 @@ class ClientUserInterface {
protocol::ErrorCode error) = 0;
virtual void OnConnectionReady(bool ready) = 0;
+ // Passes the final set of capabilities negotiated between the client and host
+ // to the application.
+ virtual void SetCapabilities(const std::string& capabilities) = 0;
+
// Get the view's ClipboardStub implementation.
virtual protocol::ClipboardStub* GetClipboardStub() = 0;
diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc
index 45671e4..bc204ca 100644
--- a/remoting/client/plugin/chromoting_instance.cc
+++ b/remoting/client/plugin/chromoting_instance.cc
@@ -137,12 +137,15 @@ logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
} // namespace
-// String sent in the "hello" message to the plugin to describe features.
+// String sent in the "hello" message to the webapp to describe features.
const char ChromotingInstance::kApiFeatures[] =
"highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
"notifyClientDimensions notifyClientResolution pauseVideo pauseAudio "
"asyncPin thirdPartyAuth";
+const char ChromotingInstance::kRequestedCapabilities[] = "";
+const char ChromotingInstance::kSupportedCapabilities[] = "";
+
bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str,
ClientConfig* config) {
std::vector<std::string> auth_methods;
@@ -191,6 +194,9 @@ ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
data->SetInteger("apiVersion", kApiVersion);
data->SetString("apiFeatures", kApiFeatures);
data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
+ data->SetString("requestedCapabilities", kRequestedCapabilities);
+ data->SetString("supportedCapabilities", kSupportedCapabilities);
+
PostChromotingMessage("hello", data.Pass());
}
@@ -297,6 +303,15 @@ void ChromotingInstance::HandleMessage(const pp::Var& message) {
config.fetch_secret_callback =
base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
}
+
+ // Read the list of capabilities, if any.
+ if (data->HasKey("capabilities")) {
+ if (!data->GetString("capabilities", &config.capabilities)) {
+ LOG(ERROR) << "Invalid connect() data.";
+ return;
+ }
+ }
+
Connect(config);
} else if (method == "disconnect") {
Disconnect();
@@ -478,6 +493,12 @@ void ChromotingInstance::OnConnectionReady(bool ready) {
PostChromotingMessage("onConnectionReady", data.Pass());
}
+void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
+ scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
+ data->SetString("capabilities", capabilities);
+ PostChromotingMessage("setCapabilities", data.Pass());
+}
+
void ChromotingInstance::FetchSecretFromDialog(
const protocol::SecretFetchedCallback& secret_fetched_callback) {
// Once the Session object calls this function, it won't continue the
diff --git a/remoting/client/plugin/chromoting_instance.h b/remoting/client/plugin/chromoting_instance.h
index c6c8f578f..dd19cc1 100644
--- a/remoting/client/plugin/chromoting_instance.h
+++ b/remoting/client/plugin/chromoting_instance.h
@@ -34,6 +34,7 @@
#include "remoting/client/plugin/pepper_input_handler.h"
#include "remoting/client/plugin/pepper_plugin_thread_delegate.h"
#include "remoting/proto/event.pb.h"
+#include "remoting/protocol/client_stub.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/cursor_shape_stub.h"
@@ -80,6 +81,14 @@ class ChromotingInstance :
// without bumping the API version.
static const char kApiFeatures[];
+ // Capabilities supported by the plugin that should also be supported by the
+ // webapp to be enabled.
+ static const char kRequestedCapabilities[];
+
+ // Capabilities supported by the plugin that do not need to be supported by
+ // the webapp to be enabled.
+ static const char kSupportedCapabilities[];
+
// Backward-compatibility version used by for the messaging
// interface. Should be updated whenever we remove support for
// an older version of the API.
@@ -108,6 +117,7 @@ class ChromotingInstance :
virtual void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) OVERRIDE;
virtual void OnConnectionReady(bool ready) OVERRIDE;
+ virtual void SetCapabilities(const std::string& capabilities) OVERRIDE;
virtual protocol::ClipboardStub* GetClipboardStub() OVERRIDE;
virtual protocol::CursorShapeStub* GetCursorShapeStub() OVERRIDE;
virtual scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
diff --git a/remoting/host/basic_desktop_environment.cc b/remoting/host/basic_desktop_environment.cc
index 9174e04..daef98a 100644
--- a/remoting/host/basic_desktop_environment.cc
+++ b/remoting/host/basic_desktop_environment.cc
@@ -46,6 +46,13 @@ scoped_ptr<ScreenControls> BasicDesktopEnvironment::CreateScreenControls() {
return scoped_ptr<ScreenControls>();
}
+std::string BasicDesktopEnvironment::GetCapabilities() const {
+ return std::string();
+}
+
+void BasicDesktopEnvironment::SetCapabilities(const std::string& capabilities) {
+}
+
scoped_ptr<media::ScreenCapturer>
BasicDesktopEnvironment::CreateVideoCapturer() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
diff --git a/remoting/host/basic_desktop_environment.h b/remoting/host/basic_desktop_environment.h
index f5de55b..b73bba1 100644
--- a/remoting/host/basic_desktop_environment.h
+++ b/remoting/host/basic_desktop_environment.h
@@ -5,6 +5,8 @@
#ifndef REMOTING_HOST_BASIC_DESKTOP_ENVIRONMENT_H_
#define REMOTING_HOST_BASIC_DESKTOP_ENVIRONMENT_H_
+#include <string>
+
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/ref_counted.h"
@@ -27,6 +29,8 @@ class BasicDesktopEnvironment : public DesktopEnvironment {
virtual scoped_ptr<InputInjector> CreateInputInjector() OVERRIDE;
virtual scoped_ptr<ScreenControls> CreateScreenControls() OVERRIDE;
virtual scoped_ptr<media::ScreenCapturer> CreateVideoCapturer() OVERRIDE;
+ virtual std::string GetCapabilities() const OVERRIDE;
+ virtual void SetCapabilities(const std::string& capabilities) OVERRIDE;
protected:
friend class BasicDesktopEnvironmentFactory;
diff --git a/remoting/host/chromoting_host_unittest.cc b/remoting/host/chromoting_host_unittest.cc
index b15fb8d..3f7768f 100644
--- a/remoting/host/chromoting_host_unittest.cc
+++ b/remoting/host/chromoting_host_unittest.cc
@@ -33,6 +33,7 @@ using ::remoting::protocol::SessionConfig;
using testing::_;
using testing::AnyNumber;
+using testing::AtMost;
using testing::AtLeast;
using testing::CreateFunctor;
using testing::DeleteArg;
@@ -283,13 +284,17 @@ class ChromotingHostTest : public testing::Test {
EXPECT_CALL(*desktop_environment, CreateAudioCapturerPtr())
.Times(0);
EXPECT_CALL(*desktop_environment, CreateInputInjectorPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(this, &ChromotingHostTest::CreateInputInjector));
+ .Times(AtMost(1))
+ .WillOnce(Invoke(this, &ChromotingHostTest::CreateInputInjector));
EXPECT_CALL(*desktop_environment, CreateScreenControlsPtr())
- .Times(AnyNumber());
+ .Times(AtMost(1));
EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(this, &ChromotingHostTest::CreateVideoCapturer));
+ .Times(AtMost(1))
+ .WillOnce(Invoke(this, &ChromotingHostTest::CreateVideoCapturer));
+ EXPECT_CALL(*desktop_environment, GetCapabilities())
+ .Times(AtMost(1));
+ EXPECT_CALL(*desktop_environment, SetCapabilities(_))
+ .Times(AtMost(1));
return desktop_environment;
}
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index 58b49a5..24cad35 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -8,6 +8,7 @@
#include "base/message_loop_proxy.h"
#include "media/video/capture/screen/screen_capturer.h"
+#include "remoting/base/capabilities.h"
#include "remoting/codec/audio_encoder.h"
#include "remoting/codec/audio_encoder_opus.h"
#include "remoting/codec/audio_encoder_speex.h"
@@ -96,6 +97,8 @@ ClientSession::~ClientSession() {
void ClientSession::NotifyClientResolution(
const protocol::ClientResolution& resolution) {
+ DCHECK(CalledOnValidThread());
+
if (!resolution.has_dips_width() || !resolution.has_dips_height())
return;
@@ -116,6 +119,8 @@ void ClientSession::NotifyClientResolution(
}
void ClientSession::ControlVideo(const protocol::VideoControl& video_control) {
+ DCHECK(CalledOnValidThread());
+
if (video_control.has_enable()) {
VLOG(1) << "Received VideoControl (enable="
<< video_control.enable() << ")";
@@ -125,6 +130,8 @@ void ClientSession::ControlVideo(const protocol::VideoControl& video_control) {
}
void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) {
+ DCHECK(CalledOnValidThread());
+
if (audio_control.has_enable()) {
VLOG(1) << "Received AudioControl (enable="
<< audio_control.enable() << ")";
@@ -133,10 +140,42 @@ void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) {
}
}
+void ClientSession::SetCapabilities(
+ const protocol::Capabilities& capabilities) {
+ DCHECK(CalledOnValidThread());
+
+ // The client should not send protocol::Capabilities if it is not supported by
+ // the config channel.
+ if (!connection_->session()->config().SupportsCapabilities()) {
+ LOG(ERROR) << "Unexpected protocol::Capabilities has been received.";
+ return;
+ }
+
+ // Ignore all the messages but the 1st one.
+ if (client_capabilities_) {
+ LOG(WARNING) << "protocol::Capabilities has been received already.";
+ return;
+ }
+
+ client_capabilities_ = make_scoped_ptr(new std::string());
+ if (capabilities.has_capabilities())
+ *client_capabilities_ = capabilities.capabilities();
+
+ VLOG(1) << "Client capabilities: " << *client_capabilities_;
+
+ // Calculate the set of capabilities enabled by both client and host and
+ // pass it to the desktop environment if it is available.
+ if (desktop_environment_) {
+ desktop_environment_->SetCapabilities(
+ IntersectCapabilities(*client_capabilities_, host_capabilities_));
+ }
+}
+
void ClientSession::OnConnectionAuthenticated(
protocol::ConnectionToClient* connection) {
DCHECK(CalledOnValidThread());
DCHECK_EQ(connection_.get(), connection);
+ DCHECK(!desktop_environment_);
auth_input_filter_.set_enabled(true);
auth_clipboard_filter_.set_enabled(true);
@@ -151,6 +190,8 @@ void ClientSession::OnConnectionAuthenticated(
this, &ClientSession::DisconnectSession);
}
+ // The session may be destroyed as the result result of this call, so it must
+ // be the last in this method.
event_handler_->OnSessionAuthenticated(this);
}
@@ -159,13 +200,35 @@ void ClientSession::OnConnectionChannelsConnected(
DCHECK(CalledOnValidThread());
DCHECK_EQ(connection_.get(), connection);
DCHECK(!audio_scheduler_);
- DCHECK(!desktop_environment_);
DCHECK(!input_injector_);
DCHECK(!screen_controls_);
DCHECK(!video_scheduler_);
+ // Create the desktop environment.
desktop_environment_ =
desktop_environment_factory_->Create(control_factory_.GetWeakPtr());
+ host_capabilities_ = desktop_environment_->GetCapabilities();
+
+ // Negotiate capabilities with the client.
+ if (connection_->session()->config().SupportsCapabilities()) {
+ VLOG(1) << "Host capabilities: " << host_capabilities_;
+
+ protocol::Capabilities capabilities;
+ capabilities.set_capabilities(host_capabilities_);
+ connection_->client_stub()->SetCapabilities(capabilities);
+
+ // |client_capabilities_| could have been received before all channels were
+ // connected. Process them now.
+ if (client_capabilities_) {
+ desktop_environment_->SetCapabilities(
+ IntersectCapabilities(*client_capabilities_, host_capabilities_));
+ }
+ } else {
+ VLOG(1) << "The client does not support any capabilities.";
+
+ client_capabilities_ = make_scoped_ptr(new std::string());
+ desktop_environment_->SetCapabilities(*client_capabilities_);
+ }
// Create the object that controls the screen resolution.
screen_controls_ = desktop_environment_->CreateScreenControls();
diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h
index da850bc..778243d 100644
--- a/remoting/host/client_session.h
+++ b/remoting/host/client_session.h
@@ -5,7 +5,7 @@
#ifndef REMOTING_HOST_CLIENT_SESSION_H_
#define REMOTING_HOST_CLIENT_SESSION_H_
-#include <list>
+#include <string>
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
@@ -104,6 +104,8 @@ class ClientSession
const protocol::VideoControl& video_control) OVERRIDE;
virtual void ControlAudio(
const protocol::AudioControl& audio_control) OVERRIDE;
+ virtual void SetCapabilities(
+ const protocol::Capabilities& capabilities) OVERRIDE;
// protocol::ConnectionToClient::EventHandler interface.
virtual void OnConnectionAuthenticated(
@@ -209,6 +211,12 @@ class ClientSession
scoped_refptr<AudioScheduler> audio_scheduler_;
scoped_refptr<VideoScheduler> video_scheduler_;
+ // The set of all capabilities supported by the client.
+ scoped_ptr<std::string> client_capabilities_;
+
+ // The set of all capabilities supported by the host.
+ std::string host_capabilities_;
+
// Used to inject mouse and keyboard input and handle clipboard events.
scoped_ptr<InputInjector> input_injector_;
diff --git a/remoting/host/client_session_unittest.cc b/remoting/host/client_session_unittest.cc
index e12758e..a5c687b 100644
--- a/remoting/host/client_session_unittest.cc
+++ b/remoting/host/client_session_unittest.cc
@@ -26,6 +26,7 @@ using protocol::SessionConfig;
using testing::_;
using testing::AnyNumber;
+using testing::AtMost;
using testing::DeleteArg;
using testing::DoAll;
using testing::Expectation;
@@ -190,9 +191,13 @@ DesktopEnvironment* ClientSessionTest::CreateDesktopEnvironment() {
EXPECT_CALL(*desktop_environment, CreateInputInjectorPtr())
.WillOnce(Invoke(this, &ClientSessionTest::CreateInputInjector));
EXPECT_CALL(*desktop_environment, CreateScreenControlsPtr())
- .Times(1);
+ .Times(AtMost(1));
EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
.WillOnce(Invoke(this, &ClientSessionTest::CreateVideoCapturer));
+ EXPECT_CALL(*desktop_environment, GetCapabilities())
+ .Times(AtMost(1));
+ EXPECT_CALL(*desktop_environment, SetCapabilities(_))
+ .Times(AtMost(1));
return desktop_environment;
}
diff --git a/remoting/host/desktop_environment.h b/remoting/host/desktop_environment.h
index 0df2e01a..0e8b480 100644
--- a/remoting/host/desktop_environment.h
+++ b/remoting/host/desktop_environment.h
@@ -40,6 +40,13 @@ class DesktopEnvironment {
virtual scoped_ptr<InputInjector> CreateInputInjector() = 0;
virtual scoped_ptr<ScreenControls> CreateScreenControls() = 0;
virtual scoped_ptr<media::ScreenCapturer> CreateVideoCapturer() = 0;
+
+ // Returns the set of all capabilities supported by |this|.
+ virtual std::string GetCapabilities() const = 0;
+
+ // Passes the final set of capabilities negotiated between the client and host
+ // to |this|.
+ virtual void SetCapabilities(const std::string& capabilities) = 0;
};
// Used to create |DesktopEnvironment| instances.
diff --git a/remoting/host/desktop_process_unittest.cc b/remoting/host/desktop_process_unittest.cc
index bc50988..0c019be 100644
--- a/remoting/host/desktop_process_unittest.cc
+++ b/remoting/host/desktop_process_unittest.cc
@@ -30,6 +30,7 @@
using testing::_;
using testing::AnyNumber;
+using testing::AtMost;
using testing::InSequence;
using testing::Return;
@@ -198,13 +199,17 @@ DesktopEnvironment* DesktopProcessTest::CreateDesktopEnvironment() {
EXPECT_CALL(*desktop_environment, CreateAudioCapturerPtr())
.Times(0);
EXPECT_CALL(*desktop_environment, CreateInputInjectorPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(this, &DesktopProcessTest::CreateInputInjector));
+ .Times(AtMost(1))
+ .WillOnce(Invoke(this, &DesktopProcessTest::CreateInputInjector));
EXPECT_CALL(*desktop_environment, CreateScreenControlsPtr())
- .Times(AnyNumber());
+ .Times(AtMost(1));
EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(this, &DesktopProcessTest::CreateVideoCapturer));
+ .Times(AtMost(1))
+ .WillOnce(Invoke(this, &DesktopProcessTest::CreateVideoCapturer));
+ EXPECT_CALL(*desktop_environment, GetCapabilities())
+ .Times(AtMost(1));
+ EXPECT_CALL(*desktop_environment, SetCapabilities(_))
+ .Times(AtMost(1));
// Notify the test that the desktop environment has been created.
network_listener_.OnDesktopEnvironmentCreated();
diff --git a/remoting/host/desktop_session_proxy.cc b/remoting/host/desktop_session_proxy.cc
index 04add54..a61ab7c 100644
--- a/remoting/host/desktop_session_proxy.cc
+++ b/remoting/host/desktop_session_proxy.cc
@@ -12,6 +12,7 @@
#include "ipc/ipc_channel_proxy.h"
#include "ipc/ipc_message_macros.h"
#include "media/video/capture/screen/screen_capture_data.h"
+#include "remoting/base/capabilities.h"
#include "remoting/host/chromoting_messages.h"
#include "remoting/host/client_session.h"
#include "remoting/host/client_session_control.h"
@@ -28,6 +29,8 @@
#include "base/win/scoped_handle.h"
#endif // defined(OS_WIN)
+const char kSendInitialResolution[] = "sendInitialResolution";
+
namespace remoting {
DesktopSessionProxy::DesktopSessionProxy(
@@ -35,14 +38,19 @@ DesktopSessionProxy::DesktopSessionProxy(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
- base::WeakPtr<ClientSessionControl> client_session_control)
+ base::WeakPtr<ClientSessionControl> client_session_control,
+ base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
+ bool virtual_terminal)
: audio_capture_task_runner_(audio_capture_task_runner),
caller_task_runner_(caller_task_runner),
io_task_runner_(io_task_runner),
video_capture_task_runner_(video_capture_task_runner),
client_session_control_(client_session_control),
+ desktop_session_connector_(desktop_session_connector),
desktop_process_(base::kNullProcessHandle),
- pending_capture_frame_requests_(0) {
+ pending_capture_frame_requests_(0),
+ is_desktop_session_connected_(false),
+ virtual_terminal_(virtual_terminal) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
}
@@ -70,6 +78,33 @@ scoped_ptr<media::ScreenCapturer> DesktopSessionProxy::CreateVideoCapturer() {
return scoped_ptr<media::ScreenCapturer>(new IpcVideoFrameCapturer(this));
}
+std::string DesktopSessionProxy::GetCapabilities() const {
+ // Ask the client to send it's resolution unconditionally.
+ return virtual_terminal_ ? kSendInitialResolution : std::string();
+}
+
+void DesktopSessionProxy::SetCapabilities(const std::string& capabilities) {
+ // Delay creation of the desktop session until the client screen resolution is
+ // received if the desktop session requires the initial screen resolution
+ // (when |virtual_terminal_| is true) and the client is expected to
+ // sent its screen resolution (the 'sendInitialResolution' capability is
+ // supported).
+ if (virtual_terminal_ &&
+ HasCapability(capabilities, kSendInitialResolution)) {
+ VLOG(1) << "Waiting for the client screen resolution.";
+ return;
+ }
+
+ // Connect to the desktop session.
+ if (!is_desktop_session_connected_) {
+ is_desktop_session_connected_ = true;
+ if (desktop_session_connector_) {
+ desktop_session_connector_->ConnectTerminal(this, screen_resolution_,
+ virtual_terminal_);
+ }
+ }
+}
+
bool DesktopSessionProxy::OnMessageReceived(const IPC::Message& message) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
@@ -288,36 +323,35 @@ void DesktopSessionProxy::SetScreenResolution(
const ScreenResolution& resolution) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
+ if (!resolution.IsValid())
+ return;
+
screen_resolution_ = resolution;
- if (!screen_resolution_.IsValid())
+
+ // Connect to the desktop session if it is not done yet.
+ if (!is_desktop_session_connected_) {
+ is_desktop_session_connected_ = true;
+ if (desktop_session_connector_) {
+ desktop_session_connector_->ConnectTerminal(this, screen_resolution_,
+ virtual_terminal_);
+ }
return;
+ }
// Pass the client's resolution to both daemon and desktop session agent.
- // Depending on the session kind the screen resolution ccan be set by either
+ // Depending on the session kind the screen resolution can be set by either
// the daemon (for example RDP sessions on Windows) or by the desktop session
// agent (when sharing the physical console).
if (desktop_session_connector_)
- desktop_session_connector_->SetScreenResolution(this, resolution);
+ desktop_session_connector_->SetScreenResolution(this, screen_resolution_);
SendToDesktop(
- new ChromotingNetworkDesktopMsg_SetScreenResolution(resolution));
-}
-
-void DesktopSessionProxy::ConnectToDesktopSession(
- base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
- bool virtual_terminal) {
- DCHECK(caller_task_runner_->BelongsToCurrentThread());
- DCHECK(!desktop_session_connector_);
- DCHECK(desktop_session_connector);
-
- desktop_session_connector_ = desktop_session_connector;
- desktop_session_connector_->ConnectTerminal(
- this, ScreenResolution(), virtual_terminal);
+ new ChromotingNetworkDesktopMsg_SetScreenResolution(screen_resolution_));
}
DesktopSessionProxy::~DesktopSessionProxy() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
- if (desktop_session_connector_)
+ if (desktop_session_connector_ && is_desktop_session_connected_)
desktop_session_connector_->DisconnectTerminal(this);
if (desktop_process_ != base::kNullProcessHandle) {
diff --git a/remoting/host/desktop_session_proxy.h b/remoting/host/desktop_session_proxy.h
index c172228..34eac55 100644
--- a/remoting/host/desktop_session_proxy.h
+++ b/remoting/host/desktop_session_proxy.h
@@ -70,13 +70,17 @@ class DesktopSessionProxy
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner,
- base::WeakPtr<ClientSessionControl> client_session_control);
+ base::WeakPtr<ClientSessionControl> client_session_control,
+ base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
+ bool virtual_terminal);
// Mirrors DesktopEnvironment.
scoped_ptr<AudioCapturer> CreateAudioCapturer();
scoped_ptr<InputInjector> CreateInputInjector();
scoped_ptr<ScreenControls> CreateScreenControls();
scoped_ptr<media::ScreenCapturer> CreateVideoCapturer();
+ std::string GetCapabilities() const;
+ void SetCapabilities(const std::string& capabilities);
// IPC::Listener implementation.
virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
@@ -87,11 +91,6 @@ class DesktopSessionProxy
bool AttachToDesktop(base::ProcessHandle desktop_process,
IPC::PlatformFileForTransit desktop_pipe);
- // Binds |this| to a desktop session.
- void ConnectToDesktopSession(
- base::WeakPtr<DesktopSessionConnector> desktop_session_connector,
- bool virtual_terminal);
-
// Closes the connection to the desktop session agent and cleans up
// the associated resources.
void DetachFromDesktop();
@@ -183,7 +182,7 @@ class DesktopSessionProxy
// Used to disconnect the client session.
base::WeakPtr<ClientSessionControl> client_session_control_;
- // Used to bind to a desktop session and receive notifications every time
+ // Used to create a desktop session and receive notifications every time
// the desktop process is replaced.
base::WeakPtr<DesktopSessionConnector> desktop_session_connector_;
@@ -205,6 +204,11 @@ class DesktopSessionProxy
// desktop session agent.
ScreenResolution screen_resolution_;
+ // True if |this| has been connected to the desktop session.
+ bool is_desktop_session_connected_;
+
+ bool virtual_terminal_;
+
DISALLOW_COPY_AND_ASSIGN(DesktopSessionProxy);
};
diff --git a/remoting/host/host_mock_objects.h b/remoting/host/host_mock_objects.h
index 0dd4020..499b198 100644
--- a/remoting/host/host_mock_objects.h
+++ b/remoting/host/host_mock_objects.h
@@ -34,6 +34,8 @@ class MockDesktopEnvironment : public DesktopEnvironment {
MOCK_METHOD0(CreateInputInjectorPtr, InputInjector*());
MOCK_METHOD0(CreateScreenControlsPtr, ScreenControls*());
MOCK_METHOD0(CreateVideoCapturerPtr, media::ScreenCapturer*());
+ MOCK_CONST_METHOD0(GetCapabilities, std::string());
+ MOCK_METHOD1(SetCapabilities, void(const std::string&));
// DesktopEnvironment implementation.
virtual scoped_ptr<AudioCapturer> CreateAudioCapturer() OVERRIDE;
diff --git a/remoting/host/ipc_desktop_environment.cc b/remoting/host/ipc_desktop_environment.cc
index 797cbd5..4254c57 100644
--- a/remoting/host/ipc_desktop_environment.cc
+++ b/remoting/host/ipc_desktop_environment.cc
@@ -36,10 +36,9 @@ IpcDesktopEnvironment::IpcDesktopEnvironment(
caller_task_runner,
io_task_runner,
capture_task_runner,
- client_session_control);
-
- desktop_session_proxy_->ConnectToDesktopSession(desktop_session_connector,
- virtual_terminal);
+ client_session_control,
+ desktop_session_connector,
+ virtual_terminal);
}
IpcDesktopEnvironment::~IpcDesktopEnvironment() {
@@ -61,6 +60,14 @@ scoped_ptr<media::ScreenCapturer> IpcDesktopEnvironment::CreateVideoCapturer() {
return desktop_session_proxy_->CreateVideoCapturer();
}
+std::string IpcDesktopEnvironment::GetCapabilities() const {
+ return desktop_session_proxy_->GetCapabilities();
+}
+
+void IpcDesktopEnvironment::SetCapabilities(const std::string& capabilities) {
+ return desktop_session_proxy_->SetCapabilities(capabilities);
+}
+
IpcDesktopEnvironmentFactory::IpcDesktopEnvironmentFactory(
scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
diff --git a/remoting/host/ipc_desktop_environment.h b/remoting/host/ipc_desktop_environment.h
index b4a8818..8a7d7d2 100644
--- a/remoting/host/ipc_desktop_environment.h
+++ b/remoting/host/ipc_desktop_environment.h
@@ -53,6 +53,8 @@ class IpcDesktopEnvironment : public DesktopEnvironment {
virtual scoped_ptr<InputInjector> CreateInputInjector() OVERRIDE;
virtual scoped_ptr<ScreenControls> CreateScreenControls() OVERRIDE;
virtual scoped_ptr<media::ScreenCapturer> CreateVideoCapturer() OVERRIDE;
+ virtual std::string GetCapabilities() const OVERRIDE;
+ virtual void SetCapabilities(const std::string& capabilities) OVERRIDE;
private:
scoped_refptr<DesktopSessionProxy> desktop_session_proxy_;
diff --git a/remoting/host/ipc_desktop_environment_unittest.cc b/remoting/host/ipc_desktop_environment_unittest.cc
index ec6c257..cd4346f 100644
--- a/remoting/host/ipc_desktop_environment_unittest.cc
+++ b/remoting/host/ipc_desktop_environment_unittest.cc
@@ -36,6 +36,7 @@
using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
+using testing::AtMost;
using testing::Return;
using testing::ReturnRef;
@@ -277,6 +278,8 @@ void IpcDesktopEnvironmentTest::SetUp() {
// Create the screen capturer.
video_capturer_ =
desktop_environment_->CreateVideoCapturer();
+
+ desktop_environment_->SetCapabilities(std::string());
}
void IpcDesktopEnvironmentTest::ConnectTerminal(
@@ -302,15 +305,19 @@ DesktopEnvironment* IpcDesktopEnvironmentTest::CreateDesktopEnvironment() {
EXPECT_CALL(*desktop_environment, CreateAudioCapturerPtr())
.Times(0);
EXPECT_CALL(*desktop_environment, CreateInputInjectorPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(
+ .Times(AtMost(1))
+ .WillOnce(Invoke(
this, &IpcDesktopEnvironmentTest::CreateInputInjector));
EXPECT_CALL(*desktop_environment, CreateScreenControlsPtr())
- .Times(AnyNumber());
+ .Times(AtMost(1));
EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
- .Times(AnyNumber())
- .WillRepeatedly(Invoke(
+ .Times(AtMost(1))
+ .WillOnce(Invoke(
this, &IpcDesktopEnvironmentTest::CreateVideoCapturer));
+ EXPECT_CALL(*desktop_environment, GetCapabilities())
+ .Times(AtMost(1));
+ EXPECT_CALL(*desktop_environment, SetCapabilities(_))
+ .Times(AtMost(1));
// Let tests know that the remote desktop environment is created.
message_loop_.PostTask(FROM_HERE, setup_run_loop_->QuitClosure());
diff --git a/remoting/proto/control.proto b/remoting/proto/control.proto
index e1c29ce..88eda00 100644
--- a/remoting/proto/control.proto
+++ b/remoting/proto/control.proto
@@ -47,3 +47,9 @@ message CursorShapeInfo {
// Cursor pixmap data in 32-bit BGRA format.
optional bytes data = 5;
}
+
+message Capabilities {
+ // List of capabilities supported by the sender (case sensitive, capabilities
+ // are separated by spaces).
+ optional string capabilities = 1;
+}
diff --git a/remoting/proto/internal.proto b/remoting/proto/internal.proto
index 21f32da..5775798 100644
--- a/remoting/proto/internal.proto
+++ b/remoting/proto/internal.proto
@@ -21,6 +21,7 @@ message ControlMessage {
optional CursorShapeInfo cursor_shape = 4;
optional VideoControl video_control = 3;
optional AudioControl audio_control = 5;
+ optional Capabilities capabilities = 6;
}
// Defines an event message on the event channel.
diff --git a/remoting/protocol/client_control_dispatcher.cc b/remoting/protocol/client_control_dispatcher.cc
index 6830981..580ea28 100644
--- a/remoting/protocol/client_control_dispatcher.cc
+++ b/remoting/protocol/client_control_dispatcher.cc
@@ -60,6 +60,13 @@ void ClientControlDispatcher::ControlAudio(const AudioControl& audio_control) {
writer_.Write(SerializeAndFrameMessage(message), base::Closure());
}
+void ClientControlDispatcher::SetCapabilities(
+ const Capabilities& capabilities) {
+ ControlMessage message;
+ message.mutable_capabilities()->CopyFrom(capabilities);
+ writer_.Write(SerializeAndFrameMessage(message), base::Closure());
+}
+
void ClientControlDispatcher::OnMessageReceived(
scoped_ptr<ControlMessage> message, const base::Closure& done_task) {
DCHECK(client_stub_);
@@ -68,6 +75,8 @@ void ClientControlDispatcher::OnMessageReceived(
if (message->has_clipboard_event()) {
clipboard_stub_->InjectClipboardEvent(message->clipboard_event());
+ } else if (message->has_capabilities()) {
+ client_stub_->SetCapabilities(message->capabilities());
} else if (message->has_cursor_shape()) {
client_stub_->SetCursorShape(message->cursor_shape());
} else {
diff --git a/remoting/protocol/client_control_dispatcher.h b/remoting/protocol/client_control_dispatcher.h
index a1bc79b..dce4d9b 100644
--- a/remoting/protocol/client_control_dispatcher.h
+++ b/remoting/protocol/client_control_dispatcher.h
@@ -38,6 +38,7 @@ class ClientControlDispatcher : public ChannelDispatcherBase,
const ClientResolution& resolution) OVERRIDE;
virtual void ControlVideo(const VideoControl& video_control) OVERRIDE;
virtual void ControlAudio(const AudioControl& audio_control) OVERRIDE;
+ virtual void SetCapabilities(const Capabilities& capabilities) OVERRIDE;
// Sets the ClientStub that will be called for each incoming control
// message. |client_stub| must outlive this object.
diff --git a/remoting/protocol/client_stub.h b/remoting/protocol/client_stub.h
index ed4681d..e357448 100644
--- a/remoting/protocol/client_stub.h
+++ b/remoting/protocol/client_stub.h
@@ -17,14 +17,16 @@
namespace remoting {
namespace protocol {
+class Capabilities;
+
class ClientStub : public ClipboardStub,
public CursorShapeStub {
public:
ClientStub() {}
virtual ~ClientStub() {}
- // Currently we don't use the control channel for anything. Add new
- // message handlers here when necessary.
+ // Passes the set of capabilities supported by the host to the client.
+ virtual void SetCapabilities(const Capabilities& capabilities) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(ClientStub);
diff --git a/remoting/protocol/host_control_dispatcher.cc b/remoting/protocol/host_control_dispatcher.cc
index db041b4..a213ac6 100644
--- a/remoting/protocol/host_control_dispatcher.cc
+++ b/remoting/protocol/host_control_dispatcher.cc
@@ -32,6 +32,13 @@ void HostControlDispatcher::OnInitialized() {
writer_.Init(channel(), BufferedSocketWriter::WriteFailedCallback());
}
+void HostControlDispatcher::SetCapabilities(
+ const Capabilities& capabilities) {
+ ControlMessage message;
+ message.mutable_capabilities()->CopyFrom(capabilities);
+ writer_.Write(SerializeAndFrameMessage(message), base::Closure());
+}
+
void HostControlDispatcher::InjectClipboardEvent(const ClipboardEvent& event) {
ControlMessage message;
message.mutable_clipboard_event()->CopyFrom(event);
@@ -60,6 +67,8 @@ void HostControlDispatcher::OnMessageReceived(
host_stub_->ControlVideo(message->video_control());
} else if (message->has_audio_control()) {
host_stub_->ControlAudio(message->audio_control());
+ } else if (message->has_capabilities()) {
+ host_stub_->SetCapabilities(message->capabilities());
} else {
LOG(WARNING) << "Unknown control message received.";
}
diff --git a/remoting/protocol/host_control_dispatcher.h b/remoting/protocol/host_control_dispatcher.h
index 080d6dd..ff0fc34 100644
--- a/remoting/protocol/host_control_dispatcher.h
+++ b/remoting/protocol/host_control_dispatcher.h
@@ -33,6 +33,9 @@ class HostControlDispatcher : public ChannelDispatcherBase,
HostControlDispatcher();
virtual ~HostControlDispatcher();
+ // ClientStub implementation.
+ virtual void SetCapabilities(const Capabilities& capabilities) OVERRIDE;
+
// ClipboardStub implementation for sending clipboard data to client.
virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
diff --git a/remoting/protocol/host_stub.h b/remoting/protocol/host_stub.h
index b66558f..03f3b08 100644
--- a/remoting/protocol/host_stub.h
+++ b/remoting/protocol/host_stub.h
@@ -14,6 +14,7 @@
namespace remoting {
namespace protocol {
+class Capabilities;
class ClientResolution;
class VideoControl;
class AudioControl;
@@ -34,6 +35,9 @@ class HostStub {
// channel is supported.
virtual void ControlAudio(const AudioControl& audio_control) = 0;
+ // Passes the set of capabilities supported by the client to the host.
+ virtual void SetCapabilities(const Capabilities& capabilities) = 0;
+
protected:
virtual ~HostStub() {}
diff --git a/remoting/protocol/protocol_mock_objects.h b/remoting/protocol/protocol_mock_objects.h
index 9d1e4a2..7ccf664 100644
--- a/remoting/protocol/protocol_mock_objects.h
+++ b/remoting/protocol/protocol_mock_objects.h
@@ -104,10 +104,9 @@ class MockHostStub : public HostStub {
MOCK_METHOD1(NotifyClientResolution,
void(const ClientResolution& resolution));
- MOCK_METHOD1(ControlVideo,
- void(const VideoControl& video_control));
- MOCK_METHOD1(ControlAudio,
- void(const AudioControl& audio_control));
+ MOCK_METHOD1(ControlVideo, void(const VideoControl& video_control));
+ MOCK_METHOD1(ControlAudio, void(const AudioControl& audio_control));
+ MOCK_METHOD1(SetCapabilities, void(const Capabilities& capabilities));
private:
DISALLOW_COPY_AND_ASSIGN(MockHostStub);
@@ -118,6 +117,9 @@ class MockClientStub : public ClientStub {
MockClientStub();
virtual ~MockClientStub();
+ // ClientStub mock implementation.
+ MOCK_METHOD1(SetCapabilities, void(const Capabilities& capabilities));
+
// ClipboardStub mock implementation.
MOCK_METHOD1(InjectClipboardEvent, void(const ClipboardEvent& event));
diff --git a/remoting/protocol/session_config.cc b/remoting/protocol/session_config.cc
index 1f05a83..de1ddc3 100644
--- a/remoting/protocol/session_config.cc
+++ b/remoting/protocol/session_config.cc
@@ -11,6 +11,10 @@ namespace protocol {
const int kDefaultStreamVersion = 2;
+// The control channel version that supports the "capabilities" message.
+const int kControlStreamVersion = 3;
+const int kControlStreamVersionNoCapabilities = kDefaultStreamVersion;
+
ChannelConfig ChannelConfig::None() {
return ChannelConfig();
}
@@ -35,14 +39,17 @@ bool ChannelConfig::operator==(const ChannelConfig& b) const {
}
SessionConfig::SessionConfig() {
+}
+bool SessionConfig::SupportsCapabilities() const {
+ return control_config_.version >= kControlStreamVersion;
}
// static
SessionConfig SessionConfig::ForTest() {
SessionConfig result;
result.set_control_config(ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
- kDefaultStreamVersion,
+ kControlStreamVersionNoCapabilities,
ChannelConfig::CODEC_UNDEFINED));
result.set_event_config(ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
kDefaultStreamVersion,
@@ -171,11 +178,15 @@ scoped_ptr<CandidateSessionConfig> CandidateSessionConfig::CreateDefault() {
// Control channel.
result->mutable_control_configs()->push_back(
ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
- kDefaultStreamVersion,
+ kControlStreamVersion,
+ ChannelConfig::CODEC_UNDEFINED));
+ result->mutable_control_configs()->push_back(
+ ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
+ kControlStreamVersionNoCapabilities,
ChannelConfig::CODEC_UNDEFINED));
result->mutable_control_configs()->push_back(
ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
- kDefaultStreamVersion,
+ kControlStreamVersionNoCapabilities,
ChannelConfig::CODEC_UNDEFINED));
// Event channel.
diff --git a/remoting/protocol/session_config.h b/remoting/protocol/session_config.h
index bca9800..ac5f0c1 100644
--- a/remoting/protocol/session_config.h
+++ b/remoting/protocol/session_config.h
@@ -82,6 +82,9 @@ class SessionConfig {
return audio_config_.transport != ChannelConfig::TRANSPORT_NONE;
}
+ // Returns true if the control channel supports capabilities.
+ bool SupportsCapabilities() const;
+
// Returns a suitable session configuration for use in tests.
static SessionConfig ForTest();
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 3f6ca1e..0a39d0c 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -2225,12 +2225,14 @@
# depend on chromotocol_proto_lib for headers.
'hard_dependency': 1,
'sources': [
+ 'base/auth_token_util.cc',
+ 'base/auth_token_util.h',
'base/auto_thread.cc',
'base/auto_thread.h',
'base/auto_thread_task_runner.cc',
'base/auto_thread_task_runner.h',
- 'base/auth_token_util.cc',
- 'base/auth_token_util.h',
+ 'base/capabilities.cc',
+ 'base/capabilities.h',
'base/compound_buffer.cc',
'base/compound_buffer.h',
'base/constants.cc',
@@ -2517,6 +2519,7 @@
'base/auto_thread_task_runner_unittest.cc',
'base/auto_thread_unittest.cc',
'base/breakpad_win_unittest.cc',
+ 'base/capabilities_unittest.cc',
'base/compound_buffer_unittest.cc',
'base/rate_counter_unittest.cc',
'base/resources_unittest.cc',
diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js
index 896d039..fe3a9cd 100644
--- a/remoting/webapp/client_plugin.js
+++ b/remoting/webapp/client_plugin.js
@@ -31,6 +31,8 @@ remoting.ClientPlugin.prototype.onConnectionStatusUpdateHandler;
remoting.ClientPlugin.prototype.onConnectionReadyHandler;
/** @type {function(): void} Desktop size change callback. */
remoting.ClientPlugin.prototype.onDesktopSizeUpdateHandler;
+/** @type {function(!Array.<string>): void} Capabilities negotiated callback. */
+remoting.ClientPlugin.prototype.onSetCapabilitiesHandler;
/** @type {function(): void} Request a PIN from the user. */
remoting.ClientPlugin.prototype.fetchPinHandler;
diff --git a/remoting/webapp/client_plugin_async.js b/remoting/webapp/client_plugin_async.js
index e9b1f65..71ec21f 100644
--- a/remoting/webapp/client_plugin_async.js
+++ b/remoting/webapp/client_plugin_async.js
@@ -41,6 +41,8 @@ remoting.ClientPluginAsync = function(plugin) {
/** @param {boolean} ready Connection ready state. */
this.onConnectionReadyHandler = function(ready) {};
this.onDesktopSizeUpdateHandler = function () {};
+ /** @param {!Array.<string>} capabilities The negotiated capabilities. */
+ this.onSetCapabilitiesHandler = function (capabilities) {};
this.fetchPinHandler = function () {};
/** @type {number} */
@@ -49,6 +51,8 @@ remoting.ClientPluginAsync = function(plugin) {
this.pluginApiFeatures_ = [];
/** @type {number} */
this.pluginApiMinVersion_ = -1;
+ /** @type {!Array.<string>} */
+ this.capabilities_ = [];
/** @type {boolean} */
this.helloReceived_ = false;
/** @type {function(boolean)|null} */
@@ -98,6 +102,17 @@ remoting.ClientPluginAsync.prototype.handleMessage_ = function(messageStr) {
return;
}
+ /**
+ * Splits a string into a list of words delimited by spaces.
+ * @param {string} str String that should be split.
+ * @return {!Array.<string>} List of words.
+ */
+ var tokenize = function(str) {
+ /** @type {Array.<string>} */
+ var tokens = str.match(/\S+/g);
+ return tokens ? tokens : [];
+ };
+
if (message.method == 'hello') {
// Reset the size in case we had to enlarge it to support click-to-play.
this.plugin.width = 0;
@@ -108,13 +123,47 @@ remoting.ClientPluginAsync.prototype.handleMessage_ = function(messageStr) {
return;
}
this.pluginApiVersion_ = /** @type {number} */ message.data['apiVersion'];
+
if (this.pluginApiVersion_ >= 7) {
if (typeof message.data['apiFeatures'] != 'string') {
console.error('Received invalid hello message: ' + messageStr);
return;
}
this.pluginApiFeatures_ =
- /** @type {Array.<string>} */ message.data['apiFeatures'].split(' ');
+ /** @type {Array.<string>} */ tokenize(message.data['apiFeatures']);
+
+ // Negotiate capabilities.
+
+ /** @type {!Array.<string>} */
+ var requestedCapabilities = [];
+ if ('requestedCapabilities' in message.data) {
+ if (typeof message.data['requestedCapabilities'] != 'string') {
+ console.error('Received invalid hello message: ' + messageStr);
+ return;
+ }
+ requestedCapabilities = tokenize(message.data['requestedCapabilities']);
+ }
+
+ /** @type {!Array.<string>} */
+ var supportedCapabilities = [];
+ if ('supportedCapabilities' in message.data) {
+ if (typeof message.data['supportedCapabilities'] != 'string') {
+ console.error('Received invalid hello message: ' + messageStr);
+ return;
+ }
+ supportedCapabilities = tokenize(message.data['supportedCapabilities']);
+ }
+
+ // At the moment the webapp does not recognize any of
+ // 'requestedCapabilities' capabilities (so they all should be disabled)
+ // and do not care about any of 'supportedCapabilities' capabilities (so
+ // they all can be enabled).
+ this.capabilities_ = supportedCapabilities;
+
+ // Let the host know that the webapp can be requested to always send
+ // the client's dimensions.
+ this.capabilities_.push(
+ remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION);
} else if (this.pluginApiVersion_ >= 6) {
this.pluginApiFeatures_ = ['highQualityScaling', 'injectKeyEvent'];
} else {
@@ -208,6 +257,15 @@ remoting.ClientPluginAsync.prototype.handleMessage_ = function(messageStr) {
this.onConnectionReadyHandler(ready);
} else if (message.method == 'fetchPin') {
this.fetchPinHandler();
+ } else if (message.method == 'setCapabilities') {
+ if (typeof message.data['capabilities'] != 'string') {
+ console.error('Received incorrect setCapabilities message.');
+ return;
+ }
+
+ /** @type {!Array.<string>} */
+ var capabilities = tokenize(message.data['capabilities']);
+ this.onSetCapabilitiesHandler(capabilities);
}
};
@@ -306,7 +364,8 @@ remoting.ClientPluginAsync.prototype.connect = function(
localJid: localJid,
sharedSecret: sharedSecret,
authenticationMethods: authenticationMethods,
- authenticationTag: authenticationTag
+ authenticationTag: authenticationTag,
+ capabilities: this.capabilities_.join(" ")
}
}));
};
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index 5284e53..a05e2e1 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -228,6 +228,39 @@ remoting.ClientSession.prototype.error_ =
remoting.ClientSession.prototype.PLUGIN_ID = 'session-client-plugin';
/**
+ * Set of capabilities for which hasCapability_() can be used to test.
+ *
+ * @enum {string}
+ */
+remoting.ClientSession.Capability = {
+ // When enabled this capability causes the client to send its screen
+ // resolution to the host once connection has been established. See
+ // this.plugin.notifyClientResolution().
+ SEND_INITIAL_RESOLUTION: 'sendInitialResolution'
+};
+
+/**
+ * The set of capabilities negotiated between the client and host.
+ * @type {Array.<string>}
+ * @private
+ */
+remoting.ClientSession.prototype.capabilities_ = null;
+
+/**
+ * @param {remoting.ClientSession.Capability} capability The capability to test
+ * for.
+ * @return {boolean} True if the capability has been negotiated between
+ * the client and host.
+ * @private
+ */
+remoting.ClientSession.prototype.hasCapability_ = function(capability) {
+ if (this.capabilities_ == null)
+ return false;
+
+ return this.capabilities_.indexOf(capability) > -1;
+};
+
+/**
* @param {Element} container The element to add the plugin to.
* @param {string} id Id to use for the plugin element .
* @return {remoting.ClientPlugin} Create plugin object for the locally
@@ -369,6 +402,8 @@ remoting.ClientSession.prototype.onPluginInitialized_ = function(initialized) {
this.onConnectionReady_.bind(this);
this.plugin.onDesktopSizeUpdateHandler =
this.onDesktopSizeChanged_.bind(this);
+ this.plugin.onSetCapabilitiesHandler =
+ this.onSetCapabilities_.bind(this);
this.connectPluginToWcs_();
};
@@ -761,6 +796,29 @@ remoting.ClientSession.prototype.onConnectionReady_ = function(ready) {
}
/**
+ * Called when the client-host capabilities negotiation is complete.
+ *
+ * @param {!Array.<string>} capabilities The set of capabilities negotiated
+ * between the client and host.
+ * @return {void} Nothing.
+ * @private
+ */
+remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) {
+ if (this.capabilities_ != null) {
+ console.error('onSetCapabilities_() is called more than once');
+ return;
+ }
+
+ this.capabilities_ = capabilities;
+ if (this.hasCapability_(
+ remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION)) {
+ this.plugin.notifyClientResolution(window.innerWidth,
+ window.innerHeight,
+ window.devicePixelRatio);
+ }
+};
+
+/**
* @private
* @param {remoting.ClientSession.State} newState The new state for the session.
* @return {void} Nothing.