client_session.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "remoting/host/client_session.h" 6 7#include <algorithm> 8 9#include "base/message_loop/message_loop_proxy.h" 10#include "remoting/base/capabilities.h" 11#include "remoting/codec/audio_encoder.h" 12#include "remoting/codec/audio_encoder_opus.h" 13#include "remoting/codec/audio_encoder_speex.h" 14#include "remoting/codec/audio_encoder_verbatim.h" 15#include "remoting/codec/video_encoder.h" 16#include "remoting/codec/video_encoder_verbatim.h" 17#include "remoting/codec/video_encoder_vp8.h" 18#include "remoting/host/audio_capturer.h" 19#include "remoting/host/audio_scheduler.h" 20#include "remoting/host/desktop_environment.h" 21#include "remoting/host/input_injector.h" 22#include "remoting/host/screen_controls.h" 23#include "remoting/host/screen_resolution.h" 24#include "remoting/host/video_scheduler.h" 25#include "remoting/proto/control.pb.h" 26#include "remoting/proto/event.pb.h" 27#include "remoting/protocol/client_stub.h" 28#include "remoting/protocol/clipboard_thread_proxy.h" 29#include "remoting/protocol/pairing_registry.h" 30#include "third_party/webrtc/modules/desktop_capture/screen_capturer.h" 31 32// Default DPI to assume for old clients that use notifyClientDimensions. 33const int kDefaultDPI = 96; 34 35namespace remoting { 36 37ClientSession::ClientSession( 38 EventHandler* event_handler, 39 scoped_refptr<base::SingleThreadTaskRunner> audio_task_runner, 40 scoped_refptr<base::SingleThreadTaskRunner> input_task_runner, 41 scoped_refptr<base::SingleThreadTaskRunner> video_capture_task_runner, 42 scoped_refptr<base::SingleThreadTaskRunner> video_encode_task_runner, 43 scoped_refptr<base::SingleThreadTaskRunner> network_task_runner, 44 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner, 45 scoped_ptr<protocol::ConnectionToClient> connection, 46 DesktopEnvironmentFactory* desktop_environment_factory, 47 const base::TimeDelta& max_duration, 48 scoped_refptr<protocol::PairingRegistry> pairing_registry) 49 : event_handler_(event_handler), 50 connection_(connection.Pass()), 51 client_jid_(connection_->session()->jid()), 52 control_factory_(this), 53 desktop_environment_factory_(desktop_environment_factory), 54 input_tracker_(&host_input_filter_), 55 remote_input_filter_(&input_tracker_), 56 mouse_clamping_filter_(&remote_input_filter_), 57 disable_input_filter_(mouse_clamping_filter_.input_filter()), 58 disable_clipboard_filter_(clipboard_echo_filter_.host_filter()), 59 auth_input_filter_(&disable_input_filter_), 60 auth_clipboard_filter_(&disable_clipboard_filter_), 61 client_clipboard_factory_(clipboard_echo_filter_.client_filter()), 62 max_duration_(max_duration), 63 audio_task_runner_(audio_task_runner), 64 input_task_runner_(input_task_runner), 65 video_capture_task_runner_(video_capture_task_runner), 66 video_encode_task_runner_(video_encode_task_runner), 67 network_task_runner_(network_task_runner), 68 ui_task_runner_(ui_task_runner), 69 pairing_registry_(pairing_registry) { 70 connection_->SetEventHandler(this); 71 72 // TODO(sergeyu): Currently ConnectionToClient expects stubs to be 73 // set before channels are connected. Make it possible to set stubs 74 // later and set them only when connection is authenticated. 75 connection_->set_clipboard_stub(&auth_clipboard_filter_); 76 connection_->set_host_stub(this); 77 connection_->set_input_stub(&auth_input_filter_); 78 79 // |auth_*_filter_|'s states reflect whether the session is authenticated. 80 auth_input_filter_.set_enabled(false); 81 auth_clipboard_filter_.set_enabled(false); 82 83#if defined(OS_WIN) 84 // LocalInputMonitorWin filters out an echo of the injected input before it 85 // reaches |remote_input_filter_|. 86 remote_input_filter_.SetExpectLocalEcho(false); 87#endif // defined(OS_WIN) 88} 89 90ClientSession::~ClientSession() { 91 DCHECK(CalledOnValidThread()); 92 DCHECK(!audio_scheduler_.get()); 93 DCHECK(!desktop_environment_); 94 DCHECK(!input_injector_); 95 DCHECK(!screen_controls_); 96 DCHECK(!video_scheduler_.get()); 97 98 connection_.reset(); 99} 100 101void ClientSession::NotifyClientResolution( 102 const protocol::ClientResolution& resolution) { 103 DCHECK(CalledOnValidThread()); 104 105 // TODO(sergeyu): Move these checks to protocol layer. 106 if (!resolution.has_dips_width() || !resolution.has_dips_height() || 107 resolution.dips_width() < 0 || resolution.dips_height() < 0 || 108 resolution.width() <= 0 || resolution.height() <= 0) { 109 LOG(ERROR) << "Received invalid ClientResolution message."; 110 return; 111 } 112 113 VLOG(1) << "Received ClientResolution (dips_width=" 114 << resolution.dips_width() << ", dips_height=" 115 << resolution.dips_height() << ")"; 116 117 if (!screen_controls_) 118 return; 119 120 ScreenResolution client_resolution( 121 webrtc::DesktopSize(resolution.dips_width(), resolution.dips_height()), 122 webrtc::DesktopVector(kDefaultDPI, kDefaultDPI)); 123 124 // Try to match the client's resolution. 125 screen_controls_->SetScreenResolution(client_resolution); 126} 127 128void ClientSession::ControlVideo(const protocol::VideoControl& video_control) { 129 DCHECK(CalledOnValidThread()); 130 131 if (video_control.has_enable()) { 132 VLOG(1) << "Received VideoControl (enable=" 133 << video_control.enable() << ")"; 134 video_scheduler_->Pause(!video_control.enable()); 135 } 136} 137 138void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) { 139 DCHECK(CalledOnValidThread()); 140 141 if (audio_control.has_enable()) { 142 VLOG(1) << "Received AudioControl (enable=" 143 << audio_control.enable() << ")"; 144 if (audio_scheduler_.get()) 145 audio_scheduler_->Pause(!audio_control.enable()); 146 } 147} 148 149void ClientSession::SetCapabilities( 150 const protocol::Capabilities& capabilities) { 151 DCHECK(CalledOnValidThread()); 152 153 // The client should not send protocol::Capabilities if it is not supported by 154 // the config channel. 155 if (!connection_->session()->config().SupportsCapabilities()) { 156 LOG(ERROR) << "Unexpected protocol::Capabilities has been received."; 157 return; 158 } 159 160 // Ignore all the messages but the 1st one. 161 if (client_capabilities_) { 162 LOG(WARNING) << "protocol::Capabilities has been received already."; 163 return; 164 } 165 166 client_capabilities_ = make_scoped_ptr(new std::string()); 167 if (capabilities.has_capabilities()) 168 *client_capabilities_ = capabilities.capabilities(); 169 170 VLOG(1) << "Client capabilities: " << *client_capabilities_; 171 172 // Calculate the set of capabilities enabled by both client and host and 173 // pass it to the desktop environment if it is available. 174 desktop_environment_->SetCapabilities( 175 IntersectCapabilities(*client_capabilities_, host_capabilities_)); 176} 177 178void ClientSession::RequestPairing( 179 const protocol::PairingRequest& pairing_request) { 180 if (pairing_request.has_client_name()) { 181 protocol::PairingRegistry::Pairing pairing = 182 pairing_registry_->CreatePairing(pairing_request.client_name()); 183 protocol::PairingResponse pairing_response; 184 pairing_response.set_client_id(pairing.client_id()); 185 pairing_response.set_shared_secret(pairing.shared_secret()); 186 connection_->client_stub()->SetPairingResponse(pairing_response); 187 } 188} 189 190void ClientSession::OnConnectionAuthenticated( 191 protocol::ConnectionToClient* connection) { 192 DCHECK(CalledOnValidThread()); 193 DCHECK_EQ(connection_.get(), connection); 194 DCHECK(!audio_scheduler_.get()); 195 DCHECK(!desktop_environment_); 196 DCHECK(!input_injector_); 197 DCHECK(!screen_controls_); 198 DCHECK(!video_scheduler_.get()); 199 200 auth_input_filter_.set_enabled(true); 201 auth_clipboard_filter_.set_enabled(true); 202 203 clipboard_echo_filter_.set_client_stub(connection_->client_stub()); 204 mouse_clamping_filter_.set_video_stub(connection_->video_stub()); 205 206 if (max_duration_ > base::TimeDelta()) { 207 // TODO(simonmorris): Let Disconnect() tell the client that the 208 // disconnection was caused by the session exceeding its maximum duration. 209 max_duration_timer_.Start(FROM_HERE, max_duration_, 210 this, &ClientSession::DisconnectSession); 211 } 212 213 // Disconnect the session if the connection was rejected by the host. 214 if (!event_handler_->OnSessionAuthenticated(this)) { 215 DisconnectSession(); 216 return; 217 } 218 219 // Create the desktop environment. Drop the connection if it could not be 220 // created for any reason (for instance the curtain could not initialize). 221 desktop_environment_ = 222 desktop_environment_factory_->Create(control_factory_.GetWeakPtr()); 223 if (!desktop_environment_) { 224 DisconnectSession(); 225 return; 226 } 227 228 host_capabilities_ = desktop_environment_->GetCapabilities(); 229 230 // Ignore protocol::Capabilities messages from the client if it does not 231 // support any capabilities. 232 if (!connection_->session()->config().SupportsCapabilities()) { 233 VLOG(1) << "The client does not support any capabilities."; 234 235 client_capabilities_ = make_scoped_ptr(new std::string()); 236 desktop_environment_->SetCapabilities(*client_capabilities_); 237 } 238 239 // Create the object that controls the screen resolution. 240 screen_controls_ = desktop_environment_->CreateScreenControls(); 241 242 // Create the event executor. 243 input_injector_ = desktop_environment_->CreateInputInjector(); 244 245 // Connect the host clipboard and input stubs. 246 host_input_filter_.set_input_stub(input_injector_.get()); 247 clipboard_echo_filter_.set_host_stub(input_injector_.get()); 248 249 // Create a VideoEncoder based on the session's video channel configuration. 250 scoped_ptr<VideoEncoder> video_encoder = 251 CreateVideoEncoder(connection_->session()->config()); 252 253 // Create a VideoScheduler to pump frames from the capturer to the client. 254 video_scheduler_ = new VideoScheduler( 255 video_capture_task_runner_, 256 video_encode_task_runner_, 257 network_task_runner_, 258 desktop_environment_->CreateVideoCapturer(), 259 video_encoder.Pass(), 260 connection_->client_stub(), 261 &mouse_clamping_filter_); 262 263 // Create an AudioScheduler if audio is enabled, to pump audio samples. 264 if (connection_->session()->config().is_audio_enabled()) { 265 scoped_ptr<AudioEncoder> audio_encoder = 266 CreateAudioEncoder(connection_->session()->config()); 267 audio_scheduler_ = new AudioScheduler( 268 audio_task_runner_, 269 network_task_runner_, 270 desktop_environment_->CreateAudioCapturer(), 271 audio_encoder.Pass(), 272 connection_->audio_stub()); 273 } 274} 275 276void ClientSession::OnConnectionChannelsConnected( 277 protocol::ConnectionToClient* connection) { 278 DCHECK(CalledOnValidThread()); 279 DCHECK_EQ(connection_.get(), connection); 280 281 // Negotiate capabilities with the client. 282 if (connection_->session()->config().SupportsCapabilities()) { 283 VLOG(1) << "Host capabilities: " << host_capabilities_; 284 285 protocol::Capabilities capabilities; 286 capabilities.set_capabilities(host_capabilities_); 287 connection_->client_stub()->SetCapabilities(capabilities); 288 } 289 290 // Start the event executor. 291 input_injector_->Start(CreateClipboardProxy()); 292 SetDisableInputs(false); 293 294 // Start capturing the screen. 295 video_scheduler_->Start(); 296 297 // Start recording audio. 298 if (connection_->session()->config().is_audio_enabled()) 299 audio_scheduler_->Start(); 300 301 // Notify the event handler that all our channels are now connected. 302 event_handler_->OnSessionChannelsConnected(this); 303} 304 305void ClientSession::OnConnectionClosed( 306 protocol::ConnectionToClient* connection, 307 protocol::ErrorCode error) { 308 DCHECK(CalledOnValidThread()); 309 DCHECK_EQ(connection_.get(), connection); 310 311 // Ignore any further callbacks. 312 control_factory_.InvalidateWeakPtrs(); 313 314 // If the client never authenticated then the session failed. 315 if (!auth_input_filter_.enabled()) 316 event_handler_->OnSessionAuthenticationFailed(this); 317 318 // Block any further input events from the client. 319 // TODO(wez): Fix ChromotingHost::OnSessionClosed not to check our 320 // is_authenticated(), so that we can disable |auth_*_filter_| here. 321 disable_input_filter_.set_enabled(false); 322 disable_clipboard_filter_.set_enabled(false); 323 324 // Ensure that any pressed keys or buttons are released. 325 input_tracker_.ReleaseAll(); 326 327 // Stop components access the client, audio or video stubs, which are no 328 // longer valid once ConnectionToClient calls OnConnectionClosed(). 329 if (audio_scheduler_.get()) { 330 audio_scheduler_->Stop(); 331 audio_scheduler_ = NULL; 332 } 333 if (video_scheduler_.get()) { 334 video_scheduler_->Stop(); 335 video_scheduler_ = NULL; 336 } 337 338 client_clipboard_factory_.InvalidateWeakPtrs(); 339 input_injector_.reset(); 340 screen_controls_.reset(); 341 desktop_environment_.reset(); 342 343 // Notify the ChromotingHost that this client is disconnected. 344 // TODO(sergeyu): Log failure reason? 345 event_handler_->OnSessionClosed(this); 346} 347 348void ClientSession::OnSequenceNumberUpdated( 349 protocol::ConnectionToClient* connection, int64 sequence_number) { 350 DCHECK(CalledOnValidThread()); 351 DCHECK_EQ(connection_.get(), connection); 352 353 if (video_scheduler_.get()) 354 video_scheduler_->UpdateSequenceNumber(sequence_number); 355 356 event_handler_->OnSessionSequenceNumber(this, sequence_number); 357} 358 359void ClientSession::OnRouteChange( 360 protocol::ConnectionToClient* connection, 361 const std::string& channel_name, 362 const protocol::TransportRoute& route) { 363 DCHECK(CalledOnValidThread()); 364 DCHECK_EQ(connection_.get(), connection); 365 event_handler_->OnSessionRouteChange(this, channel_name, route); 366} 367 368const std::string& ClientSession::client_jid() const { 369 return client_jid_; 370} 371 372void ClientSession::DisconnectSession() { 373 DCHECK(CalledOnValidThread()); 374 DCHECK(connection_.get()); 375 376 max_duration_timer_.Stop(); 377 378 // This triggers OnConnectionClosed(), and the session may be destroyed 379 // as the result, so this call must be the last in this method. 380 connection_->Disconnect(); 381} 382 383void ClientSession::OnLocalMouseMoved(const SkIPoint& position) { 384 DCHECK(CalledOnValidThread()); 385 remote_input_filter_.LocalMouseMoved(position); 386} 387 388void ClientSession::SetDisableInputs(bool disable_inputs) { 389 DCHECK(CalledOnValidThread()); 390 391 if (disable_inputs) 392 input_tracker_.ReleaseAll(); 393 394 disable_input_filter_.set_enabled(!disable_inputs); 395 disable_clipboard_filter_.set_enabled(!disable_inputs); 396} 397 398scoped_ptr<protocol::ClipboardStub> ClientSession::CreateClipboardProxy() { 399 DCHECK(CalledOnValidThread()); 400 401 return scoped_ptr<protocol::ClipboardStub>( 402 new protocol::ClipboardThreadProxy( 403 client_clipboard_factory_.GetWeakPtr(), 404 base::MessageLoopProxy::current())); 405} 406 407// TODO(sergeyu): Move this to SessionManager? 408// static 409scoped_ptr<VideoEncoder> ClientSession::CreateVideoEncoder( 410 const protocol::SessionConfig& config) { 411 const protocol::ChannelConfig& video_config = config.video_config(); 412 413 if (video_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { 414 return scoped_ptr<VideoEncoder>(new remoting::VideoEncoderVerbatim()); 415 } else if (video_config.codec == protocol::ChannelConfig::CODEC_VP8) { 416 return scoped_ptr<VideoEncoder>(new remoting::VideoEncoderVp8()); 417 } 418 419 NOTIMPLEMENTED(); 420 return scoped_ptr<VideoEncoder>(); 421} 422 423// static 424scoped_ptr<AudioEncoder> ClientSession::CreateAudioEncoder( 425 const protocol::SessionConfig& config) { 426 const protocol::ChannelConfig& audio_config = config.audio_config(); 427 428 if (audio_config.codec == protocol::ChannelConfig::CODEC_VERBATIM) { 429 return scoped_ptr<AudioEncoder>(new AudioEncoderVerbatim()); 430 } else if (audio_config.codec == protocol::ChannelConfig::CODEC_SPEEX) { 431 return scoped_ptr<AudioEncoder>(new AudioEncoderSpeex()); 432 } else if (audio_config.codec == protocol::ChannelConfig::CODEC_OPUS) { 433 return scoped_ptr<AudioEncoder>(new AudioEncoderOpus()); 434 } 435 436 NOTIMPLEMENTED(); 437 return scoped_ptr<AudioEncoder>(); 438} 439 440} // namespace remoting 441