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