diff options
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. |