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.
5 #include "remoting/client/plugin/chromoting_instance.h"
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"
44 // Windows defines 'PostMessage', so we have to undef it.
45 #if defined(PostMessage)
53 // 32-bit BGRA is 4 bytes per pixel.
54 const int kBytesPerPixel
= 4;
56 const int kPerfStatsIntervalMs
= 1000;
58 std::string
ConnectionStateToString(protocol::ConnectionToHost::State state
) {
59 // Values returned by this function must match the
60 // remoting.ClientSession.State enum in JS code.
62 case protocol::ConnectionToHost::INITIALIZING
:
63 return "INITIALIZING";
64 case protocol::ConnectionToHost::CONNECTING
:
66 case protocol::ConnectionToHost::CONNECTED
:
68 case protocol::ConnectionToHost::CLOSED
:
70 case protocol::ConnectionToHost::FAILED
:
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.
81 std::string
ConnectionErrorToString(protocol::ErrorCode error
) {
82 // Values returned by this function must match the
83 // remoting.ClientSession.Error enum in JS code.
88 case protocol::PEER_IS_OFFLINE
:
89 return "HOST_IS_OFFLINE";
91 case protocol::SESSION_REJECTED
:
92 case protocol::AUTHENTICATION_FAILED
:
93 return "SESSION_REJECTED";
95 case protocol::INCOMPATIBLE_PROTOCOL
:
96 return "INCOMPATIBLE_PROTOCOL";
98 case protocol::HOST_OVERLOAD
:
99 return "HOST_OVERLOAD";
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";
107 DLOG(FATAL
) << "Unknown error code" << error
;
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.
114 bool g_logging_to_plugin
= false;
115 bool g_has_logging_instance
= false;
116 base::LazyInstance
<scoped_refptr
<base::SingleThreadTaskRunner
> >::Leaky
117 g_logging_task_runner
= LAZY_INSTANCE_INITIALIZER
;
118 base::LazyInstance
<base::WeakPtr
<ChromotingInstance
> >::Leaky
119 g_logging_instance
= LAZY_INSTANCE_INITIALIZER
;
120 base::LazyInstance
<base::Lock
>::Leaky
121 g_logging_lock
= LAZY_INSTANCE_INITIALIZER
;
122 logging::LogMessageHandlerFunction g_logging_old_handler
= NULL
;
126 // String sent in the "hello" message to the plugin to describe features.
127 const char ChromotingInstance::kApiFeatures
[] =
128 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
129 "notifyClientDimensions pauseVideo pauseAudio";
131 bool 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
);
142 if (config
->authentication_methods
.empty()) {
143 LOG(ERROR
) << "No valid authentication methods specified.";
150 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
151 : pp::Instance(pp_instance
),
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_
),
163 key_mapper_(&input_tracker_
),
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
);
170 // Resister this instance to handle debug log messsages.
171 RegisterLoggingInstance();
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());
181 ChromotingInstance::~ChromotingInstance() {
182 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
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();
188 // PepperView must be destroyed before the client.
192 client_
->Stop(base::Bind(&PluginThreadTaskRunner::Quit
,
193 plugin_task_runner_
));
195 plugin_task_runner_
->Quit();
198 // Ensure that nothing touches the plugin thread delegate after this point.
199 plugin_task_runner_
->DetachAndRunShutdownLoop();
201 // Stopping the context shuts down all chromoting threads.
205 bool ChromotingInstance::Init(uint32_t argc
,
207 const char* argv
[]) {
208 CHECK(!initialized_
);
211 VLOG(1) << "Started ChromotingInstance::Init";
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.";
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
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();
227 // Start all the threads.
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(),
238 view_
.reset(new PepperView(this, &context_
, rectangle_decoder_
.get()));
239 consumer_proxy
->Attach(view_
->AsWeakPtr());
244 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
245 if (!message
.is_string()) {
246 LOG(ERROR
) << "Received a message that is not a string.";
250 scoped_ptr
<base::Value
> json(
251 base::JSONReader::Read(message
.AsString(),
252 base::JSON_ALLOW_TRAILING_COMMAS
));
253 base::DictionaryValue
* message_dict
= NULL
;
255 base::DictionaryValue
* data
= NULL
;
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();
264 if (method
== "connect") {
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.";
279 } else if (method
== "disconnect") {
281 } else if (method
== "incomingIq") {
283 if (!data
->GetString("iq", &iq
)) {
284 LOG(ERROR
) << "Invalid onIq() data.";
288 } else if (method
== "releaseAllKeys") {
290 } else if (method
== "injectKeyEvent") {
292 bool is_pressed
= false;
293 if (!data
->GetInteger("usbKeycode", &usb_keycode
) ||
294 !data
->GetBoolean("pressed", &is_pressed
)) {
295 LOG(ERROR
) << "Invalid injectKeyEvent.";
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;
306 if (!data
->GetInteger("fromKeycode", &from_keycode
) ||
307 !data
->GetInteger("toKeycode", &to_keycode
)) {
308 LOG(ERROR
) << "Invalid remapKey.";
312 RemapKey(from_keycode
, to_keycode
);
313 } else if (method
== "trapKey") {
316 if (!data
->GetInteger("keycode", &keycode
) ||
317 !data
->GetBoolean("trap", &trap
)) {
318 LOG(ERROR
) << "Invalid trapKey.";
322 TrapKey(keycode
, trap
);
323 } else if (method
== "sendClipboardItem") {
324 std::string mime_type
;
326 if (!data
->GetString("mimeType", &mime_type
) ||
327 !data
->GetString("item", &item
)) {
328 LOG(ERROR
) << "Invalid sendClipboardItem() data.";
331 SendClipboardItem(mime_type
, item
);
332 } else if (method
== "notifyClientDimensions") {
335 if (!data
->GetInteger("width", &width
) ||
336 !data
->GetInteger("height", &height
)) {
337 LOG(ERROR
) << "Invalid notifyClientDimensions.";
340 NotifyClientDimensions(width
, height
);
341 } else if (method
== "pauseVideo") {
343 if (!data
->GetBoolean("pause", &pause
)) {
344 LOG(ERROR
) << "Invalid pauseVideo.";
348 } else if (method
== "pauseAudio") {
350 if (!data
->GetBoolean("pause", &pause
)) {
351 LOG(ERROR
) << "Invalid pauseAudio.";
358 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
359 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
361 view_
->SetView(view
);
363 mouse_input_filter_
.set_input_size(view_
->get_view_size_dips());
366 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
367 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
372 // TODO(wez): When we have a good hook into Host dimensions changes, move
374 mouse_input_filter_
.set_output_size(view_
->get_screen_size());
376 return input_handler_
.HandleInputEvent(event
);
379 void 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());
385 data
->SetInteger("x_dpi", dpi
.x());
387 data
->SetInteger("y_dpi", dpi
.y());
388 PostChromotingMessage("onDesktopSize", data
.Pass());
391 void 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());
400 void ChromotingInstance::OnConnectionReady(bool ready
) {
401 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
402 data
->SetBoolean("ready", ready
);
403 PostChromotingMessage("onConnectionReady", data
.Pass());
406 protocol::ClipboardStub
* ChromotingInstance::GetClipboardStub() {
407 // TODO(sergeyu): Move clipboard handling to a separate class.
412 protocol::CursorShapeStub
* ChromotingInstance::GetCursorShapeStub() {
413 // TODO(sergeyu): Move cursor shape code to a separate class.
418 void 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());
426 void 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()) {
436 int width
= cursor_shape
.width();
437 int height
= cursor_shape
.height();
439 if (width
< 0 || height
< 0) {
443 if (width
> 32 || height
> 32) {
444 VLOG(2) << "Cursor too large for SetCursor: "
445 << width
<< "x" << height
<< " > 32x32";
449 if (pp::ImageData::GetNativeImageDataFormat() !=
450 PP_IMAGEDATAFORMAT_BGRA_PREMUL
) {
451 VLOG(2) << "Unable to set cursor shape - non-native image format";
455 int hotspot_x
= cursor_shape
.hotspot_x();
456 int hotspot_y
= cursor_shape
.hotspot_y();
458 pp::ImageData
cursor_image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL
,
459 pp::Size(width
, height
), false);
461 int bytes_per_row
= width
* kBytesPerPixel
;
462 const uint8
* src_row_data
= reinterpret_cast<const uint8
*>(
463 cursor_shape
.data().data());
464 uint8
* dst_row_data
= reinterpret_cast<uint8
*>(cursor_image
.data());
465 for (int row
= 0; row
< height
; row
++) {
466 memcpy(dst_row_data
, src_row_data
, bytes_per_row
);
467 src_row_data
+= bytes_per_row
;
468 dst_row_data
+= cursor_image
.stride();
471 pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM
,
473 pp::Point(hotspot_x
, hotspot_y
));
476 void ChromotingInstance::OnFirstFrameReceived() {
477 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
478 PostChromotingMessage("onFirstFrameReceived", data
.Pass());
481 void ChromotingInstance::Connect(const ClientConfig
& config
) {
482 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
484 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
486 host_connection_
.reset(new protocol::ConnectionToHost(true));
487 scoped_ptr
<AudioPlayer
> audio_player(new PepperAudioPlayer(this));
488 client_
.reset(new ChromotingClient(config
, &context_
,
489 host_connection_
.get(), this,
490 rectangle_decoder_
.get(),
491 audio_player
.Pass()));
493 // Connect the input pipeline to the protocol stub & initialize components.
494 mouse_input_filter_
.set_input_stub(host_connection_
->input_stub());
495 mouse_input_filter_
.set_input_size(view_
->get_view_size_dips());
497 LOG(INFO
) << "Connecting to " << config
.host_jid
498 << ". Local jid: " << config
.local_jid
<< ".";
500 // Setup the XMPP Proxy.
501 xmpp_proxy_
= new PepperXmppProxy(
502 base::Bind(&ChromotingInstance::SendOutgoingIq
, AsWeakPtr()),
503 plugin_task_runner_
, context_
.main_task_runner());
505 scoped_ptr
<cricket::HttpPortAllocatorBase
> port_allocator(
506 PepperPortAllocator::Create(this));
507 scoped_ptr
<protocol::TransportFactory
> transport_factory(
508 new protocol::LibjingleTransportFactory(port_allocator
.Pass(), false));
510 // Kick off the connection.
511 client_
->Start(xmpp_proxy_
, transport_factory
.Pass());
513 // Start timer that periodically sends perf stats.
514 plugin_task_runner_
->PostDelayedTask(
515 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
, AsWeakPtr()),
516 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
519 void ChromotingInstance::Disconnect() {
520 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
522 // PepperView must be destroyed before the client.
525 LOG(INFO
) << "Disconnecting from host.";
527 // TODO(sergeyu): Should we disconnect asynchronously?
528 base::WaitableEvent
done_event(true, false);
529 client_
->Stop(base::Bind(&base::WaitableEvent::Signal
,
530 base::Unretained(&done_event
)));
535 // Disconnect the input pipeline and teardown the connection.
536 mouse_input_filter_
.set_input_stub(NULL
);
537 host_connection_
.reset();
540 void ChromotingInstance::OnIncomingIq(const std::string
& iq
) {
541 xmpp_proxy_
->OnIq(iq
);
544 void ChromotingInstance::ReleaseAllKeys() {
546 input_tracker_
.ReleaseAll();
549 void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent
& event
) {
550 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
552 input_tracker_
.InjectKeyEvent(event
);
555 void ChromotingInstance::RemapKey(uint32 in_usb_keycode
,
556 uint32 out_usb_keycode
) {
557 key_mapper_
.RemapKey(in_usb_keycode
, out_usb_keycode
);
560 void ChromotingInstance::TrapKey(uint32 usb_keycode
, bool trap
) {
561 key_mapper_
.TrapKey(usb_keycode
, trap
);
564 void ChromotingInstance::SendClipboardItem(const std::string
& mime_type
,
565 const std::string
& item
) {
566 if (!IsConnected()) {
569 protocol::ClipboardEvent event
;
570 event
.set_mime_type(mime_type
);
571 event
.set_data(item
);
572 host_connection_
->clipboard_stub()->InjectClipboardEvent(event
);
575 void ChromotingInstance::NotifyClientDimensions(int width
, int height
) {
576 if (!IsConnected()) {
579 protocol::ClientDimensions client_dimensions
;
580 client_dimensions
.set_width(width
);
581 client_dimensions
.set_height(height
);
582 host_connection_
->host_stub()->NotifyClientDimensions(client_dimensions
);
585 void ChromotingInstance::PauseVideo(bool pause
) {
586 if (!IsConnected()) {
589 protocol::VideoControl video_control
;
590 video_control
.set_enable(!pause
);
591 host_connection_
->host_stub()->ControlVideo(video_control
);
594 void ChromotingInstance::PauseAudio(bool pause
) {
595 if (!IsConnected()) {
598 protocol::AudioControl audio_control
;
599 audio_control
.set_enable(!pause
);
600 host_connection_
->host_stub()->ControlAudio(audio_control
);
603 ChromotingStats
* ChromotingInstance::GetStats() {
606 return client_
->GetStats();
609 void ChromotingInstance::PostChromotingMessage(
610 const std::string
& method
,
611 scoped_ptr
<base::DictionaryValue
> data
) {
612 scoped_ptr
<base::DictionaryValue
> message(new base::DictionaryValue());
613 message
->SetString("method", method
);
614 message
->Set("data", data
.release());
616 std::string message_json
;
617 base::JSONWriter::Write(message
.get(), &message_json
);
618 PostMessage(pp::Var(message_json
));
621 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode
, bool pressed
) {
622 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
623 data
->SetInteger("usbKeycode", usb_keycode
);
624 data
->SetBoolean("pressed", pressed
);
625 PostChromotingMessage("trappedKeyEvent", data
.Pass());
628 void ChromotingInstance::SendOutgoingIq(const std::string
& iq
) {
629 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
630 data
->SetString("iq", iq
);
631 PostChromotingMessage("sendOutgoingIq", data
.Pass());
634 void ChromotingInstance::SendPerfStats() {
635 if (!client_
.get()) {
639 plugin_task_runner_
->PostDelayedTask(
640 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
, AsWeakPtr()),
641 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
643 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
644 ChromotingStats
* stats
= client_
->GetStats();
645 data
->SetDouble("videoBandwidth", stats
->video_bandwidth()->Rate());
646 data
->SetDouble("videoFrameRate", stats
->video_frame_rate()->Rate());
647 data
->SetDouble("captureLatency", stats
->video_capture_ms()->Average());
648 data
->SetDouble("encodeLatency", stats
->video_encode_ms()->Average());
649 data
->SetDouble("decodeLatency", stats
->video_decode_ms()->Average());
650 data
->SetDouble("renderLatency", stats
->video_paint_ms()->Average());
651 data
->SetDouble("roundtripLatency", stats
->round_trip_ms()->Average());
652 PostChromotingMessage("onPerfStats", data
.Pass());
656 void ChromotingInstance::RegisterLogMessageHandler() {
657 base::AutoLock
lock(g_logging_lock
.Get());
659 VLOG(1) << "Registering global log handler";
661 // Record previous handler so we can call it in a chain.
662 g_logging_old_handler
= logging::GetLogMessageHandler();
664 // Set up log message handler.
665 // This is not thread-safe so we need it within our lock.
666 logging::SetLogMessageHandler(&LogToUI
);
669 void ChromotingInstance::RegisterLoggingInstance() {
670 base::AutoLock
lock(g_logging_lock
.Get());
672 // Register this instance as the one that will handle all logging calls
673 // and display them to the user.
674 // If multiple plugins are run, then the last one registered will handle all
675 // logging for all instances.
676 g_logging_instance
.Get() = weak_factory_
.GetWeakPtr();
677 g_logging_task_runner
.Get() = plugin_task_runner_
;
678 g_has_logging_instance
= true;
681 void ChromotingInstance::UnregisterLoggingInstance() {
682 base::AutoLock
lock(g_logging_lock
.Get());
684 // Don't unregister unless we're the currently registered instance.
685 if (this != g_logging_instance
.Get().get())
688 // Unregister this instance for logging.
689 g_has_logging_instance
= false;
690 g_logging_instance
.Get().reset();
691 g_logging_task_runner
.Get() = NULL
;
693 VLOG(1) << "Unregistering global log handler";
697 bool ChromotingInstance::LogToUI(int severity
, const char* file
, int line
,
698 size_t message_start
,
699 const std::string
& str
) {
700 // Note that we're reading |g_has_logging_instance| outside of a lock.
701 // This lockless read is done so that we don't needlessly slow down global
702 // logging with a lock for each log message.
704 // This lockless read is safe because:
706 // Misreading a false value (when it should be true) means that we'll simply
707 // skip processing a few log messages.
709 // Misreading a true value (when it should be false) means that we'll take
710 // the lock and check |g_logging_instance| unnecessarily. This is not
711 // problematic because we always set |g_logging_instance| inside a lock.
712 if (g_has_logging_instance
) {
713 scoped_refptr
<base::SingleThreadTaskRunner
> logging_task_runner
;
714 base::WeakPtr
<ChromotingInstance
> logging_instance
;
717 base::AutoLock
lock(g_logging_lock
.Get());
718 // If we're on the logging thread and |g_logging_to_plugin| is set then
719 // this LOG message came from handling a previous LOG message and we
720 // should skip it to avoid an infinite loop of LOG messages.
721 if (!g_logging_task_runner
.Get()->BelongsToCurrentThread() ||
722 !g_logging_to_plugin
) {
723 logging_task_runner
= g_logging_task_runner
.Get();
724 logging_instance
= g_logging_instance
.Get();
728 if (logging_task_runner
.get()) {
729 std::string message
= remoting::GetTimestampString();
730 message
+= (str
.c_str() + message_start
);
732 logging_task_runner
->PostTask(
733 FROM_HERE
, base::Bind(&ChromotingInstance::ProcessLogToUI
,
734 logging_instance
, message
));
738 if (g_logging_old_handler
)
739 return (g_logging_old_handler
)(severity
, file
, line
, message_start
, str
);
743 void ChromotingInstance::ProcessLogToUI(const std::string
& message
) {
744 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
746 // This flag (which is set only here) is used to prevent LogToUI from posting
747 // new tasks while we're in the middle of servicing a LOG call. This can
748 // happen if the call to LogDebugInfo tries to LOG anything.
749 // Since it is read on the plugin thread, we don't need to lock to set it.
750 g_logging_to_plugin
= true;
751 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
752 data
->SetString("message", message
);
753 PostChromotingMessage("logDebugMessage", data
.Pass());
754 g_logging_to_plugin
= false;
757 bool ChromotingInstance::IsConnected() {
758 return host_connection_
.get() &&
759 (host_connection_
->state() == protocol::ConnectionToHost::CONNECTED
);
762 } // namespace remoting