chromoting_instance.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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/client/plugin/chromoting_instance.h" 6 7#include <string> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/json/json_reader.h" 13#include "base/json/json_writer.h" 14#include "base/lazy_instance.h" 15#include "base/logging.h" 16#include "base/stringprintf.h" 17#include "base/strings/string_split.h" 18#include "base/synchronization/lock.h" 19#include "base/synchronization/waitable_event.h" 20#include "base/threading/thread.h" 21#include "base/values.h" 22#include "googleurl/src/gurl.h" 23#include "jingle/glue/thread_wrapper.h" 24#include "media/base/media.h" 25#include "net/socket/ssl_server_socket.h" 26#include "ppapi/cpp/completion_callback.h" 27#include "ppapi/cpp/dev/url_util_dev.h" 28#include "ppapi/cpp/input_event.h" 29#include "ppapi/cpp/mouse_cursor.h" 30#include "ppapi/cpp/rect.h" 31#include "remoting/base/constants.h" 32#include "remoting/base/util.h" 33#include "remoting/client/chromoting_client.h" 34#include "remoting/client/client_config.h" 35#include "remoting/client/frame_consumer_proxy.h" 36#include "remoting/client/plugin/pepper_audio_player.h" 37#include "remoting/client/plugin/pepper_input_handler.h" 38#include "remoting/client/plugin/pepper_port_allocator.h" 39#include "remoting/client/plugin/pepper_token_fetcher.h" 40#include "remoting/client/plugin/pepper_view.h" 41#include "remoting/client/plugin/pepper_xmpp_proxy.h" 42#include "remoting/client/rectangle_update_decoder.h" 43#include "remoting/protocol/connection_to_host.h" 44#include "remoting/protocol/host_stub.h" 45#include "remoting/protocol/libjingle_transport_factory.h" 46 47// Windows defines 'PostMessage', so we have to undef it. 48#if defined(PostMessage) 49#undef PostMessage 50#endif 51 52namespace remoting { 53 54namespace { 55 56// 32-bit BGRA is 4 bytes per pixel. 57const int kBytesPerPixel = 4; 58 59// Default DPI to assume for old clients that use notifyClientDimensions. 60const int kDefaultDPI = 96; 61 62// Interval at which to sample performance statistics. 63const int kPerfStatsIntervalMs = 1000; 64 65// URL scheme used by Chrome apps and extensions. 66const char kChromeExtensionUrlScheme[] = "chrome-extension"; 67 68std::string ConnectionStateToString(protocol::ConnectionToHost::State state) { 69 // Values returned by this function must match the 70 // remoting.ClientSession.State enum in JS code. 71 switch (state) { 72 case protocol::ConnectionToHost::INITIALIZING: 73 return "INITIALIZING"; 74 case protocol::ConnectionToHost::CONNECTING: 75 return "CONNECTING"; 76 case protocol::ConnectionToHost::AUTHENTICATED: 77 // Report the authenticated state as 'CONNECTING' to avoid changing 78 // the interface between the plugin and webapp. 79 return "CONNECTING"; 80 case protocol::ConnectionToHost::CONNECTED: 81 return "CONNECTED"; 82 case protocol::ConnectionToHost::CLOSED: 83 return "CLOSED"; 84 case protocol::ConnectionToHost::FAILED: 85 return "FAILED"; 86 } 87 NOTREACHED(); 88 return std::string(); 89} 90 91// TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp 92// and let it handle it, but it would be hard to fix it now because 93// client plugin and webapp versions may not be in sync. It should be 94// easy to do after we are finished moving the client plugin to NaCl. 95std::string ConnectionErrorToString(protocol::ErrorCode error) { 96 // Values returned by this function must match the 97 // remoting.ClientSession.Error enum in JS code. 98 switch (error) { 99 case protocol::OK: 100 return "NONE"; 101 102 case protocol::PEER_IS_OFFLINE: 103 return "HOST_IS_OFFLINE"; 104 105 case protocol::SESSION_REJECTED: 106 case protocol::AUTHENTICATION_FAILED: 107 return "SESSION_REJECTED"; 108 109 case protocol::INCOMPATIBLE_PROTOCOL: 110 return "INCOMPATIBLE_PROTOCOL"; 111 112 case protocol::HOST_OVERLOAD: 113 return "HOST_OVERLOAD"; 114 115 case protocol::CHANNEL_CONNECTION_ERROR: 116 case protocol::SIGNALING_ERROR: 117 case protocol::SIGNALING_TIMEOUT: 118 case protocol::UNKNOWN_ERROR: 119 return "NETWORK_FAILURE"; 120 } 121 DLOG(FATAL) << "Unknown error code" << error; 122 return std::string(); 123} 124 125// This flag blocks LOGs to the UI if we're already in the middle of logging 126// to the UI. This prevents a potential infinite loop if we encounter an error 127// while sending the log message to the UI. 128bool g_logging_to_plugin = false; 129bool g_has_logging_instance = false; 130base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky 131 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER; 132base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky 133 g_logging_instance = LAZY_INSTANCE_INITIALIZER; 134base::LazyInstance<base::Lock>::Leaky 135 g_logging_lock = LAZY_INSTANCE_INITIALIZER; 136logging::LogMessageHandlerFunction g_logging_old_handler = NULL; 137 138} // namespace 139 140// String sent in the "hello" message to the webapp to describe features. 141const char ChromotingInstance::kApiFeatures[] = 142 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey " 143 "notifyClientDimensions notifyClientResolution pauseVideo pauseAudio " 144 "asyncPin thirdPartyAuth"; 145 146const char ChromotingInstance::kRequestedCapabilities[] = ""; 147const char ChromotingInstance::kSupportedCapabilities[] = ""; 148 149bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str, 150 ClientConfig* config) { 151 std::vector<std::string> auth_methods; 152 base::SplitString(auth_methods_str, ',', &auth_methods); 153 for (std::vector<std::string>::iterator it = auth_methods.begin(); 154 it != auth_methods.end(); ++it) { 155 protocol::AuthenticationMethod authentication_method = 156 protocol::AuthenticationMethod::FromString(*it); 157 if (authentication_method.is_valid()) 158 config->authentication_methods.push_back(authentication_method); 159 } 160 if (config->authentication_methods.empty()) { 161 LOG(ERROR) << "No valid authentication methods specified."; 162 return false; 163 } 164 165 return true; 166} 167 168ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) 169 : pp::Instance(pp_instance), 170 initialized_(false), 171 plugin_task_runner_( 172 new PluginThreadTaskRunner(&plugin_thread_delegate_)), 173 context_(plugin_task_runner_), 174 input_tracker_(&mouse_input_filter_), 175#if defined(OS_MACOSX) 176 // On Mac we need an extra filter to inject missing keyup events. 177 // See remoting/client/plugin/mac_key_event_processor.h for more details. 178 mac_key_event_processor_(&input_tracker_), 179 key_mapper_(&mac_key_event_processor_), 180#else 181 key_mapper_(&input_tracker_), 182#endif 183 input_handler_(&key_mapper_), 184 use_async_pin_dialog_(false), 185 weak_factory_(this) { 186 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); 187 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); 188 189 // Resister this instance to handle debug log messsages. 190 RegisterLoggingInstance(); 191 192 // Send hello message. 193 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 194 data->SetInteger("apiVersion", kApiVersion); 195 data->SetString("apiFeatures", kApiFeatures); 196 data->SetInteger("apiMinVersion", kApiMinMessagingVersion); 197 data->SetString("requestedCapabilities", kRequestedCapabilities); 198 data->SetString("supportedCapabilities", kSupportedCapabilities); 199 200 PostChromotingMessage("hello", data.Pass()); 201} 202 203ChromotingInstance::~ChromotingInstance() { 204 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 205 206 // Unregister this instance so that debug log messages will no longer be sent 207 // to it. This will stop all logging in all Chromoting instances. 208 UnregisterLoggingInstance(); 209 210 // PepperView must be destroyed before the client. 211 view_.reset(); 212 213 if (client_.get()) { 214 client_->Stop(base::Bind(&PluginThreadTaskRunner::Quit, 215 plugin_task_runner_)); 216 } else { 217 plugin_task_runner_->Quit(); 218 } 219 220 // Ensure that nothing touches the plugin thread delegate after this point. 221 plugin_task_runner_->DetachAndRunShutdownLoop(); 222 223 // Stopping the context shuts down all chromoting threads. 224 context_.Stop(); 225} 226 227bool ChromotingInstance::Init(uint32_t argc, 228 const char* argn[], 229 const char* argv[]) { 230 CHECK(!initialized_); 231 initialized_ = true; 232 233 VLOG(1) << "Started ChromotingInstance::Init"; 234 235 // Check to make sure the media library is initialized. 236 // http://crbug.com/91521. 237 if (!media::IsMediaLibraryInitialized()) { 238 LOG(ERROR) << "Media library not initialized."; 239 return false; 240 } 241 242 // Check that the calling content is part of an app or extension. 243 if (!IsCallerAppOrExtension()) { 244 LOG(ERROR) << "Not an app or extension"; 245 return false; 246 } 247 248 // Enable support for SSL server sockets, which must be done as early as 249 // possible, preferably before any NSS SSL sockets (client or server) have 250 // been created. 251 // It's possible that the hosting process has already made use of SSL, in 252 // which case, there may be a slight race. 253 net::EnableSSLServerSockets(); 254 255 // Start all the threads. 256 context_.Start(); 257 258 return true; 259} 260 261void ChromotingInstance::HandleMessage(const pp::Var& message) { 262 if (!message.is_string()) { 263 LOG(ERROR) << "Received a message that is not a string."; 264 return; 265 } 266 267 scoped_ptr<base::Value> json( 268 base::JSONReader::Read(message.AsString(), 269 base::JSON_ALLOW_TRAILING_COMMAS)); 270 base::DictionaryValue* message_dict = NULL; 271 std::string method; 272 base::DictionaryValue* data = NULL; 273 if (!json.get() || 274 !json->GetAsDictionary(&message_dict) || 275 !message_dict->GetString("method", &method) || 276 !message_dict->GetDictionary("data", &data)) { 277 LOG(ERROR) << "Received invalid message:" << message.AsString(); 278 return; 279 } 280 281 if (method == "connect") { 282 ClientConfig config; 283 std::string auth_methods; 284 if (!data->GetString("hostJid", &config.host_jid) || 285 !data->GetString("hostPublicKey", &config.host_public_key) || 286 !data->GetString("localJid", &config.local_jid) || 287 !data->GetString("authenticationMethods", &auth_methods) || 288 !ParseAuthMethods(auth_methods, &config) || 289 !data->GetString("authenticationTag", &config.authentication_tag)) { 290 LOG(ERROR) << "Invalid connect() data."; 291 return; 292 } 293 if (use_async_pin_dialog_) { 294 config.fetch_secret_callback = 295 base::Bind(&ChromotingInstance::FetchSecretFromDialog, 296 this->AsWeakPtr()); 297 } else { 298 std::string shared_secret; 299 if (!data->GetString("sharedSecret", &shared_secret)) { 300 LOG(ERROR) << "sharedSecret not specified in connect()."; 301 return; 302 } 303 config.fetch_secret_callback = 304 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret); 305 } 306 307 // Read the list of capabilities, if any. 308 if (data->HasKey("capabilities")) { 309 if (!data->GetString("capabilities", &config.capabilities)) { 310 LOG(ERROR) << "Invalid connect() data."; 311 return; 312 } 313 } 314 315 Connect(config); 316 } else if (method == "disconnect") { 317 Disconnect(); 318 } else if (method == "incomingIq") { 319 std::string iq; 320 if (!data->GetString("iq", &iq)) { 321 LOG(ERROR) << "Invalid onIq() data."; 322 return; 323 } 324 OnIncomingIq(iq); 325 } else if (method == "releaseAllKeys") { 326 ReleaseAllKeys(); 327 } else if (method == "injectKeyEvent") { 328 int usb_keycode = 0; 329 bool is_pressed = false; 330 if (!data->GetInteger("usbKeycode", &usb_keycode) || 331 !data->GetBoolean("pressed", &is_pressed)) { 332 LOG(ERROR) << "Invalid injectKeyEvent."; 333 return; 334 } 335 336 protocol::KeyEvent event; 337 event.set_usb_keycode(usb_keycode); 338 event.set_pressed(is_pressed); 339 InjectKeyEvent(event); 340 } else if (method == "remapKey") { 341 int from_keycode = 0; 342 int to_keycode = 0; 343 if (!data->GetInteger("fromKeycode", &from_keycode) || 344 !data->GetInteger("toKeycode", &to_keycode)) { 345 LOG(ERROR) << "Invalid remapKey."; 346 return; 347 } 348 349 RemapKey(from_keycode, to_keycode); 350 } else if (method == "trapKey") { 351 int keycode = 0; 352 bool trap = false; 353 if (!data->GetInteger("keycode", &keycode) || 354 !data->GetBoolean("trap", &trap)) { 355 LOG(ERROR) << "Invalid trapKey."; 356 return; 357 } 358 359 TrapKey(keycode, trap); 360 } else if (method == "sendClipboardItem") { 361 std::string mime_type; 362 std::string item; 363 if (!data->GetString("mimeType", &mime_type) || 364 !data->GetString("item", &item)) { 365 LOG(ERROR) << "Invalid sendClipboardItem() data."; 366 return; 367 } 368 SendClipboardItem(mime_type, item); 369 } else if (method == "notifyClientDimensions" || 370 method == "notifyClientResolution") { 371 // notifyClientResolution's width and height are in pixels, 372 // notifyClientDimension's in DIPs, but since for the latter 373 // we assume 96dpi, DIPs and pixels are equivalent. 374 int width = 0; 375 int height = 0; 376 if (!data->GetInteger("width", &width) || 377 !data->GetInteger("height", &height) || 378 width <= 0 || height <= 0) { 379 LOG(ERROR) << "Invalid " << method << "."; 380 return; 381 } 382 383 // notifyClientResolution requires that DPI be specified. 384 // For notifyClientDimensions we assume 96dpi. 385 int x_dpi = kDefaultDPI; 386 int y_dpi = kDefaultDPI; 387 if (method == "notifyClientResolution" && 388 (!data->GetInteger("x_dpi", &x_dpi) || 389 !data->GetInteger("y_dpi", &y_dpi) || 390 x_dpi <= 0 || y_dpi <= 0)) { 391 LOG(ERROR) << "Invalid notifyClientResolution."; 392 return; 393 } 394 395 NotifyClientResolution(width, height, x_dpi, y_dpi); 396 } else if (method == "pauseVideo") { 397 bool pause = false; 398 if (!data->GetBoolean("pause", &pause)) { 399 LOG(ERROR) << "Invalid pauseVideo."; 400 return; 401 } 402 PauseVideo(pause); 403 } else if (method == "pauseAudio") { 404 bool pause = false; 405 if (!data->GetBoolean("pause", &pause)) { 406 LOG(ERROR) << "Invalid pauseAudio."; 407 return; 408 } 409 PauseAudio(pause); 410 } else if (method == "useAsyncPinDialog") { 411 use_async_pin_dialog_ = true; 412 } else if (method == "onPinFetched") { 413 std::string pin; 414 if (!data->GetString("pin", &pin)) { 415 LOG(ERROR) << "Invalid onPinFetched."; 416 return; 417 } 418 OnPinFetched(pin); 419 } else if (method == "onThirdPartyTokenFetched") { 420 std::string token; 421 std::string shared_secret; 422 if (!data->GetString("token", &token) || 423 !data->GetString("sharedSecret", &shared_secret)) { 424 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data."; 425 return; 426 } 427 OnThirdPartyTokenFetched(token, shared_secret); 428 } 429} 430 431void ChromotingInstance::DidChangeView(const pp::View& view) { 432 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 433 434 plugin_view_ = view; 435 if (view_) { 436 view_->SetView(view); 437 mouse_input_filter_.set_input_size(view_->get_view_size_dips()); 438 } 439} 440 441bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) { 442 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 443 444 if (!IsConnected()) 445 return false; 446 447 return input_handler_.HandleInputEvent(event); 448} 449 450void ChromotingInstance::SetDesktopSize(const SkISize& size, 451 const SkIPoint& dpi) { 452 mouse_input_filter_.set_output_size(size); 453 454 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 455 data->SetInteger("width", size.width()); 456 data->SetInteger("height", size.height()); 457 if (dpi.x()) 458 data->SetInteger("x_dpi", dpi.x()); 459 if (dpi.y()) 460 data->SetInteger("y_dpi", dpi.y()); 461 PostChromotingMessage("onDesktopSize", data.Pass()); 462} 463 464void ChromotingInstance::OnConnectionState( 465 protocol::ConnectionToHost::State state, 466 protocol::ErrorCode error) { 467 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 468 data->SetString("state", ConnectionStateToString(state)); 469 data->SetString("error", ConnectionErrorToString(error)); 470 PostChromotingMessage("onConnectionStatus", data.Pass()); 471} 472 473void ChromotingInstance::FetchThirdPartyToken( 474 const GURL& token_url, 475 const std::string& host_public_key, 476 const std::string& scope, 477 base::WeakPtr<PepperTokenFetcher> pepper_token_fetcher) { 478 // Once the Session object calls this function, it won't continue the 479 // authentication until the callback is called (or connection is canceled). 480 // So, it's impossible to reach this with a callback already registered. 481 DCHECK(!pepper_token_fetcher_); 482 pepper_token_fetcher_ = pepper_token_fetcher; 483 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 484 data->SetString("tokenUrl", token_url.spec()); 485 data->SetString("hostPublicKey", host_public_key); 486 data->SetString("scope", scope); 487 PostChromotingMessage("fetchThirdPartyToken", data.Pass()); 488} 489 490void ChromotingInstance::OnConnectionReady(bool ready) { 491 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 492 data->SetBoolean("ready", ready); 493 PostChromotingMessage("onConnectionReady", data.Pass()); 494} 495 496void ChromotingInstance::SetCapabilities(const std::string& capabilities) { 497 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 498 data->SetString("capabilities", capabilities); 499 PostChromotingMessage("setCapabilities", data.Pass()); 500} 501 502void ChromotingInstance::FetchSecretFromDialog( 503 const protocol::SecretFetchedCallback& secret_fetched_callback) { 504 // Once the Session object calls this function, it won't continue the 505 // authentication until the callback is called (or connection is canceled). 506 // So, it's impossible to reach this with a callback already registered. 507 DCHECK(secret_fetched_callback_.is_null()); 508 secret_fetched_callback_ = secret_fetched_callback; 509 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 510 PostChromotingMessage("fetchPin", data.Pass()); 511} 512 513void ChromotingInstance::FetchSecretFromString( 514 const std::string& shared_secret, 515 const protocol::SecretFetchedCallback& secret_fetched_callback) { 516 secret_fetched_callback.Run(shared_secret); 517} 518 519protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() { 520 // TODO(sergeyu): Move clipboard handling to a separate class. 521 // crbug.com/138108 522 return this; 523} 524 525protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() { 526 // TODO(sergeyu): Move cursor shape code to a separate class. 527 // crbug.com/138108 528 return this; 529} 530 531scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher> 532ChromotingInstance::GetTokenFetcher(const std::string& host_public_key) { 533 return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>( 534 new PepperTokenFetcher(this->AsWeakPtr(), host_public_key)); 535} 536 537void ChromotingInstance::InjectClipboardEvent( 538 const protocol::ClipboardEvent& event) { 539 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 540 data->SetString("mimeType", event.mime_type()); 541 data->SetString("item", event.data()); 542 PostChromotingMessage("injectClipboardItem", data.Pass()); 543} 544 545void ChromotingInstance::SetCursorShape( 546 const protocol::CursorShapeInfo& cursor_shape) { 547 if (!cursor_shape.has_data() || 548 !cursor_shape.has_width() || 549 !cursor_shape.has_height() || 550 !cursor_shape.has_hotspot_x() || 551 !cursor_shape.has_hotspot_y()) { 552 return; 553 } 554 555 int width = cursor_shape.width(); 556 int height = cursor_shape.height(); 557 558 if (width < 0 || height < 0) { 559 return; 560 } 561 562 if (width > 32 || height > 32) { 563 VLOG(2) << "Cursor too large for SetCursor: " 564 << width << "x" << height << " > 32x32"; 565 return; 566 } 567 568 uint32 cursor_total_bytes = width * height * kBytesPerPixel; 569 if (cursor_shape.data().size() < cursor_total_bytes) { 570 VLOG(2) << "Expected " << cursor_total_bytes << " bytes for a " 571 << width << "x" << height << " cursor. Only received " 572 << cursor_shape.data().size() << " bytes"; 573 return; 574 } 575 576 if (pp::ImageData::GetNativeImageDataFormat() != 577 PP_IMAGEDATAFORMAT_BGRA_PREMUL) { 578 VLOG(2) << "Unable to set cursor shape - non-native image format"; 579 return; 580 } 581 582 int hotspot_x = cursor_shape.hotspot_x(); 583 int hotspot_y = cursor_shape.hotspot_y(); 584 585 pp::ImageData cursor_image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, 586 pp::Size(width, height), false); 587 588 int bytes_per_row = width * kBytesPerPixel; 589 const uint8* src_row_data = reinterpret_cast<const uint8*>( 590 cursor_shape.data().data()); 591 uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image.data()); 592 for (int row = 0; row < height; row++) { 593 memcpy(dst_row_data, src_row_data, bytes_per_row); 594 src_row_data += bytes_per_row; 595 dst_row_data += cursor_image.stride(); 596 } 597 598 pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM, 599 cursor_image, 600 pp::Point(hotspot_x, hotspot_y)); 601} 602 603void ChromotingInstance::OnFirstFrameReceived() { 604 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 605 PostChromotingMessage("onFirstFrameReceived", data.Pass()); 606} 607 608void ChromotingInstance::Connect(const ClientConfig& config) { 609 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 610 611 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); 612 613 // RectangleUpdateDecoder runs on a separate thread so for now we wrap 614 // PepperView with a ref-counted proxy object. 615 scoped_refptr<FrameConsumerProxy> consumer_proxy = 616 new FrameConsumerProxy(plugin_task_runner_); 617 618 host_connection_.reset(new protocol::ConnectionToHost(true)); 619 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this)); 620 client_.reset(new ChromotingClient(config, &context_, 621 host_connection_.get(), this, 622 consumer_proxy, audio_player.Pass())); 623 624 view_.reset(new PepperView(this, &context_, client_->GetFrameProducer())); 625 consumer_proxy->Attach(view_->AsWeakPtr()); 626 if (!plugin_view_.is_null()) { 627 view_->SetView(plugin_view_); 628 } 629 630 // Connect the input pipeline to the protocol stub & initialize components. 631 mouse_input_filter_.set_input_stub(host_connection_->input_stub()); 632 mouse_input_filter_.set_input_size(view_->get_view_size_dips()); 633 634 LOG(INFO) << "Connecting to " << config.host_jid 635 << ". Local jid: " << config.local_jid << "."; 636 637 // Setup the XMPP Proxy. 638 xmpp_proxy_ = new PepperXmppProxy( 639 base::Bind(&ChromotingInstance::SendOutgoingIq, AsWeakPtr()), 640 plugin_task_runner_, context_.main_task_runner()); 641 642 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator( 643 PepperPortAllocator::Create(this)); 644 scoped_ptr<protocol::TransportFactory> transport_factory( 645 new protocol::LibjingleTransportFactory(port_allocator.Pass(), false)); 646 647 // Kick off the connection. 648 client_->Start(xmpp_proxy_, transport_factory.Pass()); 649 650 // Start timer that periodically sends perf stats. 651 plugin_task_runner_->PostDelayedTask( 652 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()), 653 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs)); 654} 655 656void ChromotingInstance::Disconnect() { 657 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 658 659 // PepperView must be destroyed before the client. 660 view_.reset(); 661 662 LOG(INFO) << "Disconnecting from host."; 663 if (client_.get()) { 664 // TODO(sergeyu): Should we disconnect asynchronously? 665 base::WaitableEvent done_event(true, false); 666 client_->Stop(base::Bind(&base::WaitableEvent::Signal, 667 base::Unretained(&done_event))); 668 done_event.Wait(); 669 client_.reset(); 670 } 671 672 // Disconnect the input pipeline and teardown the connection. 673 mouse_input_filter_.set_input_stub(NULL); 674 host_connection_.reset(); 675} 676 677void ChromotingInstance::OnIncomingIq(const std::string& iq) { 678 // Just ignore the message if it's received before Connect() is called. It's 679 // likely to be a leftover from a previous session, so it's safe to ignore it. 680 if (xmpp_proxy_) 681 xmpp_proxy_->OnIq(iq); 682} 683 684void ChromotingInstance::ReleaseAllKeys() { 685 if (IsConnected()) 686 input_tracker_.ReleaseAll(); 687} 688 689void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent& event) { 690 // Inject after the KeyEventMapper, so the event won't get mapped or trapped. 691 if (IsConnected()) 692 input_tracker_.InjectKeyEvent(event); 693} 694 695void ChromotingInstance::RemapKey(uint32 in_usb_keycode, 696 uint32 out_usb_keycode) { 697 key_mapper_.RemapKey(in_usb_keycode, out_usb_keycode); 698} 699 700void ChromotingInstance::TrapKey(uint32 usb_keycode, bool trap) { 701 key_mapper_.TrapKey(usb_keycode, trap); 702} 703 704void ChromotingInstance::SendClipboardItem(const std::string& mime_type, 705 const std::string& item) { 706 if (!IsConnected()) { 707 return; 708 } 709 protocol::ClipboardEvent event; 710 event.set_mime_type(mime_type); 711 event.set_data(item); 712 host_connection_->clipboard_stub()->InjectClipboardEvent(event); 713} 714 715void ChromotingInstance::NotifyClientResolution(int width, 716 int height, 717 int x_dpi, 718 int y_dpi) { 719 if (!IsConnected()) { 720 return; 721 } 722 723 protocol::ClientResolution client_resolution; 724 client_resolution.set_width(width); 725 client_resolution.set_height(height); 726 client_resolution.set_x_dpi(x_dpi); 727 client_resolution.set_y_dpi(y_dpi); 728 729 // Include the legacy width & height in DIPs for use by older hosts. 730 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi); 731 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi); 732 733 host_connection_->host_stub()->NotifyClientResolution(client_resolution); 734} 735 736void ChromotingInstance::PauseVideo(bool pause) { 737 if (!IsConnected()) { 738 return; 739 } 740 protocol::VideoControl video_control; 741 video_control.set_enable(!pause); 742 host_connection_->host_stub()->ControlVideo(video_control); 743} 744 745void ChromotingInstance::PauseAudio(bool pause) { 746 if (!IsConnected()) { 747 return; 748 } 749 protocol::AudioControl audio_control; 750 audio_control.set_enable(!pause); 751 host_connection_->host_stub()->ControlAudio(audio_control); 752} 753void ChromotingInstance::OnPinFetched(const std::string& pin) { 754 if (!secret_fetched_callback_.is_null()) { 755 secret_fetched_callback_.Run(pin); 756 secret_fetched_callback_.Reset(); 757 } else { 758 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch."; 759 } 760} 761 762void ChromotingInstance::OnThirdPartyTokenFetched( 763 const std::string& token, 764 const std::string& shared_secret) { 765 if (pepper_token_fetcher_) { 766 pepper_token_fetcher_->OnTokenFetched(token, shared_secret); 767 pepper_token_fetcher_.reset(); 768 } else { 769 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch."; 770 } 771} 772 773ChromotingStats* ChromotingInstance::GetStats() { 774 if (!client_.get()) 775 return NULL; 776 return client_->GetStats(); 777} 778 779void ChromotingInstance::PostChromotingMessage( 780 const std::string& method, 781 scoped_ptr<base::DictionaryValue> data) { 782 scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue()); 783 message->SetString("method", method); 784 message->Set("data", data.release()); 785 786 std::string message_json; 787 base::JSONWriter::Write(message.get(), &message_json); 788 PostMessage(pp::Var(message_json)); 789} 790 791void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) { 792 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 793 data->SetInteger("usbKeycode", usb_keycode); 794 data->SetBoolean("pressed", pressed); 795 PostChromotingMessage("trappedKeyEvent", data.Pass()); 796} 797 798void ChromotingInstance::SendOutgoingIq(const std::string& iq) { 799 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 800 data->SetString("iq", iq); 801 PostChromotingMessage("sendOutgoingIq", data.Pass()); 802} 803 804void ChromotingInstance::SendPerfStats() { 805 if (!client_.get()) { 806 return; 807 } 808 809 plugin_task_runner_->PostDelayedTask( 810 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()), 811 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs)); 812 813 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 814 ChromotingStats* stats = client_->GetStats(); 815 data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate()); 816 data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate()); 817 data->SetDouble("captureLatency", stats->video_capture_ms()->Average()); 818 data->SetDouble("encodeLatency", stats->video_encode_ms()->Average()); 819 data->SetDouble("decodeLatency", stats->video_decode_ms()->Average()); 820 data->SetDouble("renderLatency", stats->video_paint_ms()->Average()); 821 data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average()); 822 PostChromotingMessage("onPerfStats", data.Pass()); 823} 824 825// static 826void ChromotingInstance::RegisterLogMessageHandler() { 827 base::AutoLock lock(g_logging_lock.Get()); 828 829 VLOG(1) << "Registering global log handler"; 830 831 // Record previous handler so we can call it in a chain. 832 g_logging_old_handler = logging::GetLogMessageHandler(); 833 834 // Set up log message handler. 835 // This is not thread-safe so we need it within our lock. 836 logging::SetLogMessageHandler(&LogToUI); 837} 838 839void ChromotingInstance::RegisterLoggingInstance() { 840 base::AutoLock lock(g_logging_lock.Get()); 841 842 // Register this instance as the one that will handle all logging calls 843 // and display them to the user. 844 // If multiple plugins are run, then the last one registered will handle all 845 // logging for all instances. 846 g_logging_instance.Get() = weak_factory_.GetWeakPtr(); 847 g_logging_task_runner.Get() = plugin_task_runner_; 848 g_has_logging_instance = true; 849} 850 851void ChromotingInstance::UnregisterLoggingInstance() { 852 base::AutoLock lock(g_logging_lock.Get()); 853 854 // Don't unregister unless we're the currently registered instance. 855 if (this != g_logging_instance.Get().get()) 856 return; 857 858 // Unregister this instance for logging. 859 g_has_logging_instance = false; 860 g_logging_instance.Get().reset(); 861 g_logging_task_runner.Get() = NULL; 862 863 VLOG(1) << "Unregistering global log handler"; 864} 865 866// static 867bool ChromotingInstance::LogToUI(int severity, const char* file, int line, 868 size_t message_start, 869 const std::string& str) { 870 // Note that we're reading |g_has_logging_instance| outside of a lock. 871 // This lockless read is done so that we don't needlessly slow down global 872 // logging with a lock for each log message. 873 // 874 // This lockless read is safe because: 875 // 876 // Misreading a false value (when it should be true) means that we'll simply 877 // skip processing a few log messages. 878 // 879 // Misreading a true value (when it should be false) means that we'll take 880 // the lock and check |g_logging_instance| unnecessarily. This is not 881 // problematic because we always set |g_logging_instance| inside a lock. 882 if (g_has_logging_instance) { 883 scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner; 884 base::WeakPtr<ChromotingInstance> logging_instance; 885 886 { 887 base::AutoLock lock(g_logging_lock.Get()); 888 // If we're on the logging thread and |g_logging_to_plugin| is set then 889 // this LOG message came from handling a previous LOG message and we 890 // should skip it to avoid an infinite loop of LOG messages. 891 if (!g_logging_task_runner.Get()->BelongsToCurrentThread() || 892 !g_logging_to_plugin) { 893 logging_task_runner = g_logging_task_runner.Get(); 894 logging_instance = g_logging_instance.Get(); 895 } 896 } 897 898 if (logging_task_runner.get()) { 899 std::string message = remoting::GetTimestampString(); 900 message += (str.c_str() + message_start); 901 902 logging_task_runner->PostTask( 903 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI, 904 logging_instance, message)); 905 } 906 } 907 908 if (g_logging_old_handler) 909 return (g_logging_old_handler)(severity, file, line, message_start, str); 910 return false; 911} 912 913void ChromotingInstance::ProcessLogToUI(const std::string& message) { 914 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 915 916 // This flag (which is set only here) is used to prevent LogToUI from posting 917 // new tasks while we're in the middle of servicing a LOG call. This can 918 // happen if the call to LogDebugInfo tries to LOG anything. 919 // Since it is read on the plugin thread, we don't need to lock to set it. 920 g_logging_to_plugin = true; 921 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 922 data->SetString("message", message); 923 PostChromotingMessage("logDebugMessage", data.Pass()); 924 g_logging_to_plugin = false; 925} 926 927bool ChromotingInstance::IsCallerAppOrExtension() { 928 const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get(); 929 if (!url_util) 930 return false; 931 932 PP_URLComponents_Dev url_components; 933 pp::Var url_var = url_util->GetDocumentURL(this, &url_components); 934 if (!url_var.is_string()) 935 return false; 936 937 std::string url = url_var.AsString(); 938 std::string url_scheme = url.substr(url_components.scheme.begin, 939 url_components.scheme.len); 940 return url_scheme == kChromeExtensionUrlScheme; 941} 942 943bool ChromotingInstance::IsConnected() { 944 return host_connection_.get() && 945 (host_connection_->state() == protocol::ConnectionToHost::CONNECTED); 946} 947 948} // namespace remoting 949