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