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