chromoting_instance.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 <algorithm> 8#include <string> 9#include <vector> 10 11#include "base/bind.h" 12#include "base/callback.h" 13#include "base/json/json_reader.h" 14#include "base/json/json_writer.h" 15#include "base/lazy_instance.h" 16#include "base/logging.h" 17#include "base/strings/string_split.h" 18#include "base/strings/stringprintf.h" 19#include "base/synchronization/lock.h" 20#include "base/threading/thread.h" 21#include "base/values.h" 22#include "crypto/random.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/image_data.h" 29#include "ppapi/cpp/input_event.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/delegating_signal_strategy.h" 37#include "remoting/client/plugin/media_source_video_renderer.h" 38#include "remoting/client/plugin/pepper_audio_player.h" 39#include "remoting/client/plugin/pepper_input_handler.h" 40#include "remoting/client/plugin/pepper_port_allocator.h" 41#include "remoting/client/plugin/pepper_token_fetcher.h" 42#include "remoting/client/plugin/pepper_view.h" 43#include "remoting/client/software_video_renderer.h" 44#include "remoting/protocol/connection_to_host.h" 45#include "remoting/protocol/host_stub.h" 46#include "remoting/protocol/libjingle_transport_factory.h" 47#include "third_party/libjingle/source/talk/base/helpers.h" 48#include "url/gurl.h" 49 50// Windows defines 'PostMessage', so we have to undef it. 51#if defined(PostMessage) 52#undef PostMessage 53#endif 54 55namespace remoting { 56 57namespace { 58 59// 32-bit BGRA is 4 bytes per pixel. 60const int kBytesPerPixel = 4; 61 62#if defined(ARCH_CPU_LITTLE_ENDIAN) 63const uint32_t kPixelAlphaMask = 0xff000000; 64#else // !defined(ARCH_CPU_LITTLE_ENDIAN) 65const uint32_t kPixelAlphaMask = 0x000000ff; 66#endif // !defined(ARCH_CPU_LITTLE_ENDIAN) 67 68// Default DPI to assume for old clients that use notifyClientResolution. 69const int kDefaultDPI = 96; 70 71// Interval at which to sample performance statistics. 72const int kPerfStatsIntervalMs = 1000; 73 74// URL scheme used by Chrome apps and extensions. 75const char kChromeExtensionUrlScheme[] = "chrome-extension"; 76 77// Maximum width and height of a mouse cursor supported by PPAPI. 78const int kMaxCursorWidth = 32; 79const int kMaxCursorHeight = 32; 80 81#if defined(USE_OPENSSL) 82// Size of the random seed blob used to initialize RNG in libjingle. Libjingle 83// uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of 84// entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores 85// 1039 bytes of state, so we initialize it with 1k or random data. 86const int kRandomSeedSize = 1024; 87#endif // defined(USE_OPENSSL) 88 89std::string ConnectionStateToString(protocol::ConnectionToHost::State state) { 90 // Values returned by this function must match the 91 // remoting.ClientSession.State enum in JS code. 92 switch (state) { 93 case protocol::ConnectionToHost::INITIALIZING: 94 return "INITIALIZING"; 95 case protocol::ConnectionToHost::CONNECTING: 96 return "CONNECTING"; 97 case protocol::ConnectionToHost::AUTHENTICATED: 98 // Report the authenticated state as 'CONNECTING' to avoid changing 99 // the interface between the plugin and webapp. 100 return "CONNECTING"; 101 case protocol::ConnectionToHost::CONNECTED: 102 return "CONNECTED"; 103 case protocol::ConnectionToHost::CLOSED: 104 return "CLOSED"; 105 case protocol::ConnectionToHost::FAILED: 106 return "FAILED"; 107 } 108 NOTREACHED(); 109 return std::string(); 110} 111 112// TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp 113// and let it handle it, but it would be hard to fix it now because 114// client plugin and webapp versions may not be in sync. It should be 115// easy to do after we are finished moving the client plugin to NaCl. 116std::string ConnectionErrorToString(protocol::ErrorCode error) { 117 // Values returned by this function must match the 118 // remoting.ClientSession.Error enum in JS code. 119 switch (error) { 120 case protocol::OK: 121 return "NONE"; 122 123 case protocol::PEER_IS_OFFLINE: 124 return "HOST_IS_OFFLINE"; 125 126 case protocol::SESSION_REJECTED: 127 case protocol::AUTHENTICATION_FAILED: 128 return "SESSION_REJECTED"; 129 130 case protocol::INCOMPATIBLE_PROTOCOL: 131 return "INCOMPATIBLE_PROTOCOL"; 132 133 case protocol::HOST_OVERLOAD: 134 return "HOST_OVERLOAD"; 135 136 case protocol::CHANNEL_CONNECTION_ERROR: 137 case protocol::SIGNALING_ERROR: 138 case protocol::SIGNALING_TIMEOUT: 139 case protocol::UNKNOWN_ERROR: 140 return "NETWORK_FAILURE"; 141 } 142 DLOG(FATAL) << "Unknown error code" << error; 143 return std::string(); 144} 145 146// Returns true if |pixel| is not completely transparent. 147bool IsVisiblePixel(uint32_t pixel) { 148 return (pixel & kPixelAlphaMask) != 0; 149} 150 151// Returns true if there is at least one visible pixel in the given range. 152bool IsVisibleRow(const uint32_t* begin, const uint32_t* end) { 153 return std::find_if(begin, end, &IsVisiblePixel) != end; 154} 155 156// This flag blocks LOGs to the UI if we're already in the middle of logging 157// to the UI. This prevents a potential infinite loop if we encounter an error 158// while sending the log message to the UI. 159bool g_logging_to_plugin = false; 160bool g_has_logging_instance = false; 161base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky 162 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER; 163base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky 164 g_logging_instance = LAZY_INSTANCE_INITIALIZER; 165base::LazyInstance<base::Lock>::Leaky 166 g_logging_lock = LAZY_INSTANCE_INITIALIZER; 167logging::LogMessageHandlerFunction g_logging_old_handler = NULL; 168 169} // namespace 170 171// String sent in the "hello" message to the webapp to describe features. 172const char ChromotingInstance::kApiFeatures[] = 173 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey " 174 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth " 175 "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering"; 176 177const char ChromotingInstance::kRequestedCapabilities[] = ""; 178const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape"; 179 180bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str, 181 ClientConfig* config) { 182 std::vector<std::string> auth_methods; 183 base::SplitString(auth_methods_str, ',', &auth_methods); 184 for (std::vector<std::string>::iterator it = auth_methods.begin(); 185 it != auth_methods.end(); ++it) { 186 protocol::AuthenticationMethod authentication_method = 187 protocol::AuthenticationMethod::FromString(*it); 188 if (authentication_method.is_valid()) 189 config->authentication_methods.push_back(authentication_method); 190 } 191 if (config->authentication_methods.empty()) { 192 LOG(ERROR) << "No valid authentication methods specified."; 193 return false; 194 } 195 196 return true; 197} 198 199ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) 200 : pp::Instance(pp_instance), 201 initialized_(false), 202 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)), 203 context_(plugin_task_runner_.get()), 204 input_tracker_(&mouse_input_filter_), 205 key_mapper_(&input_tracker_), 206 normalizing_input_filter_(CreateNormalizingInputFilter(&key_mapper_)), 207 input_handler_(this, normalizing_input_filter_.get()), 208 use_async_pin_dialog_(false), 209 use_media_source_rendering_(false), 210 weak_factory_(this) { 211 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); 212 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); 213 214 // Resister this instance to handle debug log messsages. 215 RegisterLoggingInstance(); 216 217#if defined(USE_OPENSSL) 218 // Initialize random seed for libjingle. It's necessary only with OpenSSL. 219 char random_seed[kRandomSeedSize]; 220 crypto::RandBytes(random_seed, sizeof(random_seed)); 221 talk_base::InitRandom(random_seed, sizeof(random_seed)); 222#endif // defined(USE_OPENSSL) 223 224 // Send hello message. 225 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 226 data->SetInteger("apiVersion", kApiVersion); 227 data->SetString("apiFeatures", kApiFeatures); 228 data->SetInteger("apiMinVersion", kApiMinMessagingVersion); 229 data->SetString("requestedCapabilities", kRequestedCapabilities); 230 data->SetString("supportedCapabilities", kSupportedCapabilities); 231 232 PostLegacyJsonMessage("hello", data.Pass()); 233} 234 235ChromotingInstance::~ChromotingInstance() { 236 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 237 238 // Unregister this instance so that debug log messages will no longer be sent 239 // to it. This will stop all logging in all Chromoting instances. 240 UnregisterLoggingInstance(); 241 242 // PepperView must be destroyed before the client. 243 view_weak_factory_.reset(); 244 view_.reset(); 245 246 client_.reset(); 247 248 plugin_task_runner_->Quit(); 249 250 // Ensure that nothing touches the plugin thread delegate after this point. 251 plugin_task_runner_->DetachAndRunShutdownLoop(); 252 253 // Stopping the context shuts down all chromoting threads. 254 context_.Stop(); 255} 256 257bool ChromotingInstance::Init(uint32_t argc, 258 const char* argn[], 259 const char* argv[]) { 260 CHECK(!initialized_); 261 initialized_ = true; 262 263 VLOG(1) << "Started ChromotingInstance::Init"; 264 265 // Check to make sure the media library is initialized. 266 // http://crbug.com/91521. 267 if (!media::IsMediaLibraryInitialized()) { 268 LOG(ERROR) << "Media library not initialized."; 269 return false; 270 } 271 272 // Check that the calling content is part of an app or extension. 273 if (!IsCallerAppOrExtension()) { 274 LOG(ERROR) << "Not an app or extension"; 275 return false; 276 } 277 278 // Enable support for SSL server sockets, which must be done as early as 279 // possible, preferably before any NSS SSL sockets (client or server) have 280 // been created. 281 // It's possible that the hosting process has already made use of SSL, in 282 // which case, there may be a slight race. 283 net::EnableSSLServerSockets(); 284 285 // Start all the threads. 286 context_.Start(); 287 288 return true; 289} 290 291void ChromotingInstance::HandleMessage(const pp::Var& message) { 292 if (!message.is_string()) { 293 LOG(ERROR) << "Received a message that is not a string."; 294 return; 295 } 296 297 scoped_ptr<base::Value> json( 298 base::JSONReader::Read(message.AsString(), 299 base::JSON_ALLOW_TRAILING_COMMAS)); 300 base::DictionaryValue* message_dict = NULL; 301 std::string method; 302 base::DictionaryValue* data = NULL; 303 if (!json.get() || 304 !json->GetAsDictionary(&message_dict) || 305 !message_dict->GetString("method", &method) || 306 !message_dict->GetDictionary("data", &data)) { 307 LOG(ERROR) << "Received invalid message:" << message.AsString(); 308 return; 309 } 310 311 if (method == "connect") { 312 HandleConnect(*data); 313 } else if (method == "disconnect") { 314 HandleDisconnect(*data); 315 } else if (method == "incomingIq") { 316 HandleOnIncomingIq(*data); 317 } else if (method == "releaseAllKeys") { 318 HandleReleaseAllKeys(*data); 319 } else if (method == "injectKeyEvent") { 320 HandleInjectKeyEvent(*data); 321 } else if (method == "remapKey") { 322 HandleRemapKey(*data); 323 } else if (method == "trapKey") { 324 HandleTrapKey(*data); 325 } else if (method == "sendClipboardItem") { 326 HandleSendClipboardItem(*data); 327 } else if (method == "notifyClientResolution") { 328 HandleNotifyClientResolution(*data); 329 } else if (method == "pauseVideo") { 330 HandlePauseVideo(*data); 331 } else if (method == "pauseAudio") { 332 HandlePauseAudio(*data); 333 } else if (method == "useAsyncPinDialog") { 334 use_async_pin_dialog_ = true; 335 } else if (method == "onPinFetched") { 336 HandleOnPinFetched(*data); 337 } else if (method == "onThirdPartyTokenFetched") { 338 HandleOnThirdPartyTokenFetched(*data); 339 } else if (method == "requestPairing") { 340 HandleRequestPairing(*data); 341 } else if (method == "extensionMessage") { 342 HandleExtensionMessage(*data); 343 } else if (method == "allowMouseLock") { 344 HandleAllowMouseLockMessage(); 345 } else if (method == "enableMediaSourceRendering") { 346 HandleEnableMediaSourceRendering(); 347 } 348} 349 350void ChromotingInstance::DidChangeFocus(bool has_focus) { 351 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 352 353 input_handler_.DidChangeFocus(has_focus); 354} 355 356void ChromotingInstance::DidChangeView(const pp::View& view) { 357 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 358 359 plugin_view_ = view; 360 mouse_input_filter_.set_input_size( 361 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height())); 362 363 if (view_) 364 view_->SetView(view); 365} 366 367bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) { 368 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 369 370 if (!IsConnected()) 371 return false; 372 373 return input_handler_.HandleInputEvent(event); 374} 375 376void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize& size, 377 const webrtc::DesktopVector& dpi) { 378 mouse_input_filter_.set_output_size(size); 379 380 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 381 data->SetInteger("width", size.width()); 382 data->SetInteger("height", size.height()); 383 if (dpi.x()) 384 data->SetInteger("x_dpi", dpi.x()); 385 if (dpi.y()) 386 data->SetInteger("y_dpi", dpi.y()); 387 PostLegacyJsonMessage("onDesktopSize", data.Pass()); 388} 389 390void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion& shape) { 391 if (desktop_shape_ && shape.Equals(*desktop_shape_)) 392 return; 393 394 desktop_shape_.reset(new webrtc::DesktopRegion(shape)); 395 396 scoped_ptr<base::ListValue> rects_value(new base::ListValue()); 397 for (webrtc::DesktopRegion::Iterator i(shape); !i.IsAtEnd(); i.Advance()) { 398 const webrtc::DesktopRect& rect = i.rect(); 399 scoped_ptr<base::ListValue> rect_value(new base::ListValue()); 400 rect_value->AppendInteger(rect.left()); 401 rect_value->AppendInteger(rect.top()); 402 rect_value->AppendInteger(rect.width()); 403 rect_value->AppendInteger(rect.height()); 404 rects_value->Append(rect_value.release()); 405 } 406 407 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 408 data->Set("rects", rects_value.release()); 409 PostLegacyJsonMessage("onDesktopShape", data.Pass()); 410} 411 412void ChromotingInstance::OnConnectionState( 413 protocol::ConnectionToHost::State state, 414 protocol::ErrorCode error) { 415 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 416 data->SetString("state", ConnectionStateToString(state)); 417 data->SetString("error", ConnectionErrorToString(error)); 418 PostLegacyJsonMessage("onConnectionStatus", data.Pass()); 419} 420 421void ChromotingInstance::FetchThirdPartyToken( 422 const GURL& token_url, 423 const std::string& host_public_key, 424 const std::string& scope, 425 base::WeakPtr<PepperTokenFetcher> pepper_token_fetcher) { 426 // Once the Session object calls this function, it won't continue the 427 // authentication until the callback is called (or connection is canceled). 428 // So, it's impossible to reach this with a callback already registered. 429 DCHECK(!pepper_token_fetcher_.get()); 430 pepper_token_fetcher_ = pepper_token_fetcher; 431 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 432 data->SetString("tokenUrl", token_url.spec()); 433 data->SetString("hostPublicKey", host_public_key); 434 data->SetString("scope", scope); 435 PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass()); 436} 437 438void ChromotingInstance::OnConnectionReady(bool ready) { 439 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 440 data->SetBoolean("ready", ready); 441 PostLegacyJsonMessage("onConnectionReady", data.Pass()); 442} 443 444void ChromotingInstance::OnRouteChanged(const std::string& channel_name, 445 const protocol::TransportRoute& route) { 446 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 447 std::string message = "Channel " + channel_name + " using " + 448 protocol::TransportRoute::GetTypeString(route.type) + " connection."; 449 data->SetString("message", message); 450 PostLegacyJsonMessage("logDebugMessage", data.Pass()); 451} 452 453void ChromotingInstance::SetCapabilities(const std::string& capabilities) { 454 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 455 data->SetString("capabilities", capabilities); 456 PostLegacyJsonMessage("setCapabilities", data.Pass()); 457} 458 459void ChromotingInstance::SetPairingResponse( 460 const protocol::PairingResponse& pairing_response) { 461 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 462 data->SetString("clientId", pairing_response.client_id()); 463 data->SetString("sharedSecret", pairing_response.shared_secret()); 464 PostLegacyJsonMessage("pairingResponse", data.Pass()); 465} 466 467void ChromotingInstance::DeliverHostMessage( 468 const protocol::ExtensionMessage& message) { 469 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 470 data->SetString("type", message.type()); 471 data->SetString("data", message.data()); 472 PostLegacyJsonMessage("extensionMessage", data.Pass()); 473} 474 475void ChromotingInstance::FetchSecretFromDialog( 476 bool pairing_supported, 477 const protocol::SecretFetchedCallback& secret_fetched_callback) { 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(secret_fetched_callback_.is_null()); 482 secret_fetched_callback_ = secret_fetched_callback; 483 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 484 data->SetBoolean("pairingSupported", pairing_supported); 485 PostLegacyJsonMessage("fetchPin", data.Pass()); 486} 487 488void ChromotingInstance::FetchSecretFromString( 489 const std::string& shared_secret, 490 bool pairing_supported, 491 const protocol::SecretFetchedCallback& secret_fetched_callback) { 492 secret_fetched_callback.Run(shared_secret); 493} 494 495protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() { 496 // TODO(sergeyu): Move clipboard handling to a separate class. 497 // crbug.com/138108 498 return this; 499} 500 501protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() { 502 // TODO(sergeyu): Move cursor shape code to a separate class. 503 // crbug.com/138108 504 return this; 505} 506 507scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher> 508ChromotingInstance::GetTokenFetcher(const std::string& host_public_key) { 509 return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>( 510 new PepperTokenFetcher(weak_factory_.GetWeakPtr(), host_public_key)); 511} 512 513void ChromotingInstance::InjectClipboardEvent( 514 const protocol::ClipboardEvent& event) { 515 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 516 data->SetString("mimeType", event.mime_type()); 517 data->SetString("item", event.data()); 518 PostLegacyJsonMessage("injectClipboardItem", data.Pass()); 519} 520 521void ChromotingInstance::SetCursorShape( 522 const protocol::CursorShapeInfo& cursor_shape) { 523 COMPILE_ASSERT(sizeof(uint32_t) == kBytesPerPixel, rgba_pixels_are_32bit); 524 525 // pp::MouseCursor requires image to be in the native format. 526 if (pp::ImageData::GetNativeImageDataFormat() != 527 PP_IMAGEDATAFORMAT_BGRA_PREMUL) { 528 LOG(WARNING) << "Unable to set cursor shape - native image format is not" 529 " premultiplied BGRA"; 530 return; 531 } 532 533 int width = cursor_shape.width(); 534 int height = cursor_shape.height(); 535 536 int hotspot_x = cursor_shape.hotspot_x(); 537 int hotspot_y = cursor_shape.hotspot_y(); 538 int bytes_per_row = width * kBytesPerPixel; 539 int src_stride = width; 540 const uint32_t* src_row_data = reinterpret_cast<const uint32_t*>( 541 cursor_shape.data().data()); 542 const uint32_t* src_row_data_end = src_row_data + src_stride * height; 543 544 scoped_ptr<pp::ImageData> cursor_image; 545 pp::Point cursor_hotspot; 546 547 // Check if the cursor is visible. 548 if (IsVisibleRow(src_row_data, src_row_data_end)) { 549 // If the cursor exceeds the size permitted by PPAPI then crop it, keeping 550 // the hotspot as close to the center of the new cursor shape as possible. 551 if (height > kMaxCursorHeight) { 552 int y = hotspot_y - (kMaxCursorHeight / 2); 553 y = std::max(y, 0); 554 y = std::min(y, height - kMaxCursorHeight); 555 556 src_row_data += src_stride * y; 557 height = kMaxCursorHeight; 558 hotspot_y -= y; 559 } 560 if (width > kMaxCursorWidth) { 561 int x = hotspot_x - (kMaxCursorWidth / 2); 562 x = std::max(x, 0); 563 x = std::min(x, height - kMaxCursorWidth); 564 565 src_row_data += x; 566 width = kMaxCursorWidth; 567 bytes_per_row = width * kBytesPerPixel; 568 hotspot_x -= x; 569 } 570 571 cursor_image.reset(new pp::ImageData(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL, 572 pp::Size(width, height), false)); 573 cursor_hotspot = pp::Point(hotspot_x, hotspot_y); 574 575 uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image->data()); 576 for (int row = 0; row < height; row++) { 577 memcpy(dst_row_data, src_row_data, bytes_per_row); 578 src_row_data += src_stride; 579 dst_row_data += cursor_image->stride(); 580 } 581 } 582 583 input_handler_.SetMouseCursor(cursor_image.Pass(), cursor_hotspot); 584} 585 586void ChromotingInstance::OnFirstFrameReceived() { 587 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 588 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass()); 589} 590 591void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) { 592 ClientConfig config; 593 std::string local_jid; 594 std::string auth_methods; 595 if (!data.GetString("hostJid", &config.host_jid) || 596 !data.GetString("hostPublicKey", &config.host_public_key) || 597 !data.GetString("localJid", &local_jid) || 598 !data.GetString("authenticationMethods", &auth_methods) || 599 !ParseAuthMethods(auth_methods, &config) || 600 !data.GetString("authenticationTag", &config.authentication_tag)) { 601 LOG(ERROR) << "Invalid connect() data."; 602 return; 603 } 604 data.GetString("clientPairingId", &config.client_pairing_id); 605 data.GetString("clientPairedSecret", &config.client_paired_secret); 606 if (use_async_pin_dialog_) { 607 config.fetch_secret_callback = 608 base::Bind(&ChromotingInstance::FetchSecretFromDialog, 609 weak_factory_.GetWeakPtr()); 610 } else { 611 std::string shared_secret; 612 if (!data.GetString("sharedSecret", &shared_secret)) { 613 LOG(ERROR) << "sharedSecret not specified in connect()."; 614 return; 615 } 616 config.fetch_secret_callback = 617 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret); 618 } 619 620 // Read the list of capabilities, if any. 621 if (data.HasKey("capabilities")) { 622 if (!data.GetString("capabilities", &config.capabilities)) { 623 LOG(ERROR) << "Invalid connect() data."; 624 return; 625 } 626 } 627 628 ConnectWithConfig(config, local_jid); 629} 630 631void ChromotingInstance::ConnectWithConfig(const ClientConfig& config, 632 const std::string& local_jid) { 633 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 634 635 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop(); 636 637 if (use_media_source_rendering_) { 638 video_renderer_.reset(new MediaSourceVideoRenderer(this)); 639 } else { 640 view_.reset(new PepperView(this, &context_)); 641 view_weak_factory_.reset( 642 new base::WeakPtrFactory<FrameConsumer>(view_.get())); 643 644 // SoftwareVideoRenderer runs on a separate thread so for now we wrap 645 // PepperView with a ref-counted proxy object. 646 scoped_refptr<FrameConsumerProxy> consumer_proxy = 647 new FrameConsumerProxy(plugin_task_runner_, 648 view_weak_factory_->GetWeakPtr()); 649 650 SoftwareVideoRenderer* renderer = 651 new SoftwareVideoRenderer(context_.main_task_runner(), 652 context_.decode_task_runner(), 653 consumer_proxy); 654 view_->Initialize(renderer); 655 if (!plugin_view_.is_null()) 656 view_->SetView(plugin_view_); 657 video_renderer_.reset(renderer); 658 } 659 660 host_connection_.reset(new protocol::ConnectionToHost(true)); 661 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this)); 662 client_.reset(new ChromotingClient(config, &context_, host_connection_.get(), 663 this, video_renderer_.get(), 664 audio_player.Pass())); 665 666 // Connect the input pipeline to the protocol stub & initialize components. 667 mouse_input_filter_.set_input_stub(host_connection_->input_stub()); 668 if (!plugin_view_.is_null()) { 669 mouse_input_filter_.set_input_size(webrtc::DesktopSize( 670 plugin_view_.GetRect().width(), plugin_view_.GetRect().height())); 671 } 672 673 VLOG(0) << "Connecting to " << config.host_jid 674 << ". Local jid: " << local_jid << "."; 675 676 // Setup the signal strategy. 677 signal_strategy_.reset(new DelegatingSignalStrategy( 678 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq, 679 weak_factory_.GetWeakPtr()))); 680 681 scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator( 682 PepperPortAllocator::Create(this)); 683 scoped_ptr<protocol::TransportFactory> transport_factory( 684 new protocol::LibjingleTransportFactory( 685 signal_strategy_.get(), port_allocator.Pass(), 686 NetworkSettings(NetworkSettings::NAT_TRAVERSAL_ENABLED))); 687 688 // Kick off the connection. 689 client_->Start(signal_strategy_.get(), transport_factory.Pass()); 690 691 // Start timer that periodically sends perf stats. 692 plugin_task_runner_->PostDelayedTask( 693 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, 694 weak_factory_.GetWeakPtr()), 695 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs)); 696} 697 698void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) { 699 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 700 701 // PepperView must be destroyed before the client. 702 view_weak_factory_.reset(); 703 view_.reset(); 704 705 VLOG(0) << "Disconnecting from host."; 706 707 client_.reset(); 708 709 // Disconnect the input pipeline and teardown the connection. 710 mouse_input_filter_.set_input_stub(NULL); 711 host_connection_.reset(); 712} 713 714void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) { 715 std::string iq; 716 if (!data.GetString("iq", &iq)) { 717 LOG(ERROR) << "Invalid incomingIq() data."; 718 return; 719 } 720 721 // Just ignore the message if it's received before Connect() is called. It's 722 // likely to be a leftover from a previous session, so it's safe to ignore it. 723 if (signal_strategy_) 724 signal_strategy_->OnIncomingMessage(iq); 725} 726 727void ChromotingInstance::HandleReleaseAllKeys( 728 const base::DictionaryValue& data) { 729 if (IsConnected()) 730 input_tracker_.ReleaseAll(); 731} 732 733void ChromotingInstance::HandleInjectKeyEvent( 734 const base::DictionaryValue& data) { 735 int usb_keycode = 0; 736 bool is_pressed = false; 737 if (!data.GetInteger("usbKeycode", &usb_keycode) || 738 !data.GetBoolean("pressed", &is_pressed)) { 739 LOG(ERROR) << "Invalid injectKeyEvent."; 740 return; 741 } 742 743 protocol::KeyEvent event; 744 event.set_usb_keycode(usb_keycode); 745 event.set_pressed(is_pressed); 746 747 // Inject after the KeyEventMapper, so the event won't get mapped or trapped. 748 if (IsConnected()) 749 input_tracker_.InjectKeyEvent(event); 750} 751 752void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) { 753 int from_keycode = 0; 754 int to_keycode = 0; 755 if (!data.GetInteger("fromKeycode", &from_keycode) || 756 !data.GetInteger("toKeycode", &to_keycode)) { 757 LOG(ERROR) << "Invalid remapKey."; 758 return; 759 } 760 761 key_mapper_.RemapKey(from_keycode, to_keycode); 762} 763 764void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) { 765 int keycode = 0; 766 bool trap = false; 767 if (!data.GetInteger("keycode", &keycode) || 768 !data.GetBoolean("trap", &trap)) { 769 LOG(ERROR) << "Invalid trapKey."; 770 return; 771 } 772 773 key_mapper_.TrapKey(keycode, trap); 774} 775 776void ChromotingInstance::HandleSendClipboardItem( 777 const base::DictionaryValue& data) { 778 std::string mime_type; 779 std::string item; 780 if (!data.GetString("mimeType", &mime_type) || 781 !data.GetString("item", &item)) { 782 LOG(ERROR) << "Invalid sendClipboardItem data."; 783 return; 784 } 785 if (!IsConnected()) { 786 return; 787 } 788 protocol::ClipboardEvent event; 789 event.set_mime_type(mime_type); 790 event.set_data(item); 791 host_connection_->clipboard_stub()->InjectClipboardEvent(event); 792} 793 794void ChromotingInstance::HandleNotifyClientResolution( 795 const base::DictionaryValue& data) { 796 int width = 0; 797 int height = 0; 798 int x_dpi = kDefaultDPI; 799 int y_dpi = kDefaultDPI; 800 if (!data.GetInteger("width", &width) || 801 !data.GetInteger("height", &height) || 802 !data.GetInteger("x_dpi", &x_dpi) || 803 !data.GetInteger("y_dpi", &y_dpi) || 804 width <= 0 || height <= 0 || 805 x_dpi <= 0 || y_dpi <= 0) { 806 LOG(ERROR) << "Invalid notifyClientResolution."; 807 return; 808 } 809 810 if (!IsConnected()) { 811 return; 812 } 813 814 protocol::ClientResolution client_resolution; 815 client_resolution.set_width(width); 816 client_resolution.set_height(height); 817 client_resolution.set_x_dpi(x_dpi); 818 client_resolution.set_y_dpi(y_dpi); 819 820 // Include the legacy width & height in DIPs for use by older hosts. 821 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi); 822 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi); 823 824 host_connection_->host_stub()->NotifyClientResolution(client_resolution); 825} 826 827void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) { 828 bool pause = false; 829 if (!data.GetBoolean("pause", &pause)) { 830 LOG(ERROR) << "Invalid pauseVideo."; 831 return; 832 } 833 if (!IsConnected()) { 834 return; 835 } 836 protocol::VideoControl video_control; 837 video_control.set_enable(!pause); 838 host_connection_->host_stub()->ControlVideo(video_control); 839} 840 841void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) { 842 bool pause = false; 843 if (!data.GetBoolean("pause", &pause)) { 844 LOG(ERROR) << "Invalid pauseAudio."; 845 return; 846 } 847 if (!IsConnected()) { 848 return; 849 } 850 protocol::AudioControl audio_control; 851 audio_control.set_enable(!pause); 852 host_connection_->host_stub()->ControlAudio(audio_control); 853} 854void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) { 855 std::string pin; 856 if (!data.GetString("pin", &pin)) { 857 LOG(ERROR) << "Invalid onPinFetched."; 858 return; 859 } 860 if (!secret_fetched_callback_.is_null()) { 861 secret_fetched_callback_.Run(pin); 862 secret_fetched_callback_.Reset(); 863 } else { 864 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch."; 865 } 866} 867 868void ChromotingInstance::HandleOnThirdPartyTokenFetched( 869 const base::DictionaryValue& data) { 870 std::string token; 871 std::string shared_secret; 872 if (!data.GetString("token", &token) || 873 !data.GetString("sharedSecret", &shared_secret)) { 874 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data."; 875 return; 876 } 877 if (pepper_token_fetcher_.get()) { 878 pepper_token_fetcher_->OnTokenFetched(token, shared_secret); 879 pepper_token_fetcher_.reset(); 880 } else { 881 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch."; 882 } 883} 884 885void ChromotingInstance::HandleRequestPairing( 886 const base::DictionaryValue& data) { 887 std::string client_name; 888 if (!data.GetString("clientName", &client_name)) { 889 LOG(ERROR) << "Invalid requestPairing"; 890 return; 891 } 892 if (!IsConnected()) { 893 return; 894 } 895 protocol::PairingRequest pairing_request; 896 pairing_request.set_client_name(client_name); 897 host_connection_->host_stub()->RequestPairing(pairing_request); 898} 899 900void ChromotingInstance::HandleExtensionMessage( 901 const base::DictionaryValue& data) { 902 std::string type; 903 std::string message_data; 904 if (!data.GetString("type", &type) || 905 !data.GetString("data", &message_data)) { 906 LOG(ERROR) << "Invalid extensionMessage."; 907 return; 908 } 909 if (!IsConnected()) { 910 return; 911 } 912 protocol::ExtensionMessage message; 913 message.set_type(type); 914 message.set_data(message_data); 915 host_connection_->host_stub()->DeliverClientMessage(message); 916} 917 918void ChromotingInstance::HandleAllowMouseLockMessage() { 919 input_handler_.AllowMouseLock(); 920} 921 922void ChromotingInstance::HandleEnableMediaSourceRendering() { 923 use_media_source_rendering_ = true; 924} 925 926ChromotingStats* ChromotingInstance::GetStats() { 927 if (!video_renderer_.get()) 928 return NULL; 929 return video_renderer_->GetStats(); 930} 931 932void ChromotingInstance::PostChromotingMessage(const std::string& method, 933 const pp::VarDictionary& data) { 934 pp::VarDictionary message; 935 message.Set(pp::Var("method"), pp::Var(method)); 936 message.Set(pp::Var("data"), data); 937 PostMessage(message); 938} 939 940void ChromotingInstance::PostLegacyJsonMessage( 941 const std::string& method, 942 scoped_ptr<base::DictionaryValue> data) { 943 scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue()); 944 message->SetString("method", method); 945 message->Set("data", data.release()); 946 947 std::string message_json; 948 base::JSONWriter::Write(message.get(), &message_json); 949 PostMessage(pp::Var(message_json)); 950} 951 952void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) { 953 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 954 data->SetInteger("usbKeycode", usb_keycode); 955 data->SetBoolean("pressed", pressed); 956 PostLegacyJsonMessage("trappedKeyEvent", data.Pass()); 957} 958 959void ChromotingInstance::SendOutgoingIq(const std::string& iq) { 960 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 961 data->SetString("iq", iq); 962 PostLegacyJsonMessage("sendOutgoingIq", data.Pass()); 963} 964 965void ChromotingInstance::SendPerfStats() { 966 if (!video_renderer_.get()) { 967 return; 968 } 969 970 plugin_task_runner_->PostDelayedTask( 971 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, 972 weak_factory_.GetWeakPtr()), 973 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs)); 974 975 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 976 ChromotingStats* stats = video_renderer_->GetStats(); 977 data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate()); 978 data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate()); 979 data->SetDouble("captureLatency", stats->video_capture_ms()->Average()); 980 data->SetDouble("encodeLatency", stats->video_encode_ms()->Average()); 981 data->SetDouble("decodeLatency", stats->video_decode_ms()->Average()); 982 data->SetDouble("renderLatency", stats->video_paint_ms()->Average()); 983 data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average()); 984 PostLegacyJsonMessage("onPerfStats", data.Pass()); 985} 986 987// static 988void ChromotingInstance::RegisterLogMessageHandler() { 989 base::AutoLock lock(g_logging_lock.Get()); 990 991 VLOG(1) << "Registering global log handler"; 992 993 // Record previous handler so we can call it in a chain. 994 g_logging_old_handler = logging::GetLogMessageHandler(); 995 996 // Set up log message handler. 997 // This is not thread-safe so we need it within our lock. 998 logging::SetLogMessageHandler(&LogToUI); 999} 1000 1001void ChromotingInstance::RegisterLoggingInstance() { 1002 base::AutoLock lock(g_logging_lock.Get()); 1003 1004 // Register this instance as the one that will handle all logging calls 1005 // and display them to the user. 1006 // If multiple plugins are run, then the last one registered will handle all 1007 // logging for all instances. 1008 g_logging_instance.Get() = weak_factory_.GetWeakPtr(); 1009 g_logging_task_runner.Get() = plugin_task_runner_; 1010 g_has_logging_instance = true; 1011} 1012 1013void ChromotingInstance::UnregisterLoggingInstance() { 1014 base::AutoLock lock(g_logging_lock.Get()); 1015 1016 // Don't unregister unless we're the currently registered instance. 1017 if (this != g_logging_instance.Get().get()) 1018 return; 1019 1020 // Unregister this instance for logging. 1021 g_has_logging_instance = false; 1022 g_logging_instance.Get().reset(); 1023 g_logging_task_runner.Get() = NULL; 1024 1025 VLOG(1) << "Unregistering global log handler"; 1026} 1027 1028// static 1029bool ChromotingInstance::LogToUI(int severity, const char* file, int line, 1030 size_t message_start, 1031 const std::string& str) { 1032 // Note that we're reading |g_has_logging_instance| outside of a lock. 1033 // This lockless read is done so that we don't needlessly slow down global 1034 // logging with a lock for each log message. 1035 // 1036 // This lockless read is safe because: 1037 // 1038 // Misreading a false value (when it should be true) means that we'll simply 1039 // skip processing a few log messages. 1040 // 1041 // Misreading a true value (when it should be false) means that we'll take 1042 // the lock and check |g_logging_instance| unnecessarily. This is not 1043 // problematic because we always set |g_logging_instance| inside a lock. 1044 if (g_has_logging_instance) { 1045 scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner; 1046 base::WeakPtr<ChromotingInstance> logging_instance; 1047 1048 { 1049 base::AutoLock lock(g_logging_lock.Get()); 1050 // If we're on the logging thread and |g_logging_to_plugin| is set then 1051 // this LOG message came from handling a previous LOG message and we 1052 // should skip it to avoid an infinite loop of LOG messages. 1053 if (!g_logging_task_runner.Get()->BelongsToCurrentThread() || 1054 !g_logging_to_plugin) { 1055 logging_task_runner = g_logging_task_runner.Get(); 1056 logging_instance = g_logging_instance.Get(); 1057 } 1058 } 1059 1060 if (logging_task_runner.get()) { 1061 std::string message = remoting::GetTimestampString(); 1062 message += (str.c_str() + message_start); 1063 1064 logging_task_runner->PostTask( 1065 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI, 1066 logging_instance, message)); 1067 } 1068 } 1069 1070 if (g_logging_old_handler) 1071 return (g_logging_old_handler)(severity, file, line, message_start, str); 1072 return false; 1073} 1074 1075void ChromotingInstance::ProcessLogToUI(const std::string& message) { 1076 DCHECK(plugin_task_runner_->BelongsToCurrentThread()); 1077 1078 // This flag (which is set only here) is used to prevent LogToUI from posting 1079 // new tasks while we're in the middle of servicing a LOG call. This can 1080 // happen if the call to LogDebugInfo tries to LOG anything. 1081 // Since it is read on the plugin thread, we don't need to lock to set it. 1082 g_logging_to_plugin = true; 1083 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 1084 data->SetString("message", message); 1085 PostLegacyJsonMessage("logDebugMessage", data.Pass()); 1086 g_logging_to_plugin = false; 1087} 1088 1089bool ChromotingInstance::IsCallerAppOrExtension() { 1090 const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get(); 1091 if (!url_util) 1092 return false; 1093 1094 PP_URLComponents_Dev url_components; 1095 pp::Var url_var = url_util->GetDocumentURL(this, &url_components); 1096 if (!url_var.is_string()) 1097 return false; 1098 1099 std::string url = url_var.AsString(); 1100 std::string url_scheme = url.substr(url_components.scheme.begin, 1101 url_components.scheme.len); 1102 return url_scheme == kChromeExtensionUrlScheme; 1103} 1104 1105bool ChromotingInstance::IsConnected() { 1106 return host_connection_.get() && 1107 (host_connection_->state() == protocol::ConnectionToHost::CONNECTED); 1108} 1109 1110void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize& size, 1111 const webrtc::DesktopVector& dpi) { 1112 SetDesktopSize(size, dpi); 1113} 1114 1115void ChromotingInstance::OnMediaSourceShape( 1116 const webrtc::DesktopRegion& shape) { 1117 SetDesktopShape(shape); 1118} 1119 1120void ChromotingInstance::OnMediaSourceReset(const std::string& format) { 1121 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); 1122 data->SetString("format", format); 1123 PostLegacyJsonMessage("mediaSourceReset", data.Pass()); 1124} 1125 1126void ChromotingInstance::OnMediaSourceData(uint8_t* buffer, 1127 size_t buffer_size) { 1128 pp::VarArrayBuffer array_buffer(buffer_size); 1129 void* data_ptr = array_buffer.Map(); 1130 memcpy(data_ptr, buffer, buffer_size); 1131 array_buffer.Unmap(); 1132 pp::VarDictionary data_dictionary; 1133 data_dictionary.Set(pp::Var("buffer"), array_buffer); 1134 PostChromotingMessage("mediaSourceData", data_dictionary); 1135} 1136 1137} // namespace remoting 1138