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"
11 #include <nacl_io/nacl_io.h>
12 #include <sys/mount.h>
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/json/json_reader.h"
18 #include "base/json/json_writer.h"
19 #include "base/lazy_instance.h"
20 #include "base/logging.h"
21 #include "base/strings/string_split.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/synchronization/lock.h"
24 #include "base/threading/thread.h"
25 #include "base/values.h"
26 #include "crypto/random.h"
27 #include "jingle/glue/thread_wrapper.h"
28 #include "media/base/yuv_convert.h"
29 #include "net/socket/ssl_server_socket.h"
30 #include "ppapi/cpp/completion_callback.h"
31 #include "ppapi/cpp/dev/url_util_dev.h"
32 #include "ppapi/cpp/image_data.h"
33 #include "ppapi/cpp/input_event.h"
34 #include "ppapi/cpp/rect.h"
35 #include "ppapi/cpp/var_array_buffer.h"
36 #include "ppapi/cpp/var_dictionary.h"
37 #include "remoting/base/constants.h"
38 #include "remoting/base/util.h"
39 #include "remoting/client/chromoting_client.h"
40 #include "remoting/client/frame_consumer_proxy.h"
41 #include "remoting/client/plugin/delegating_signal_strategy.h"
42 #include "remoting/client/plugin/media_source_video_renderer.h"
43 #include "remoting/client/plugin/normalizing_input_filter_cros.h"
44 #include "remoting/client/plugin/normalizing_input_filter_mac.h"
45 #include "remoting/client/plugin/pepper_audio_player.h"
46 #include "remoting/client/plugin/pepper_mouse_locker.h"
47 #include "remoting/client/plugin/pepper_port_allocator.h"
48 #include "remoting/client/plugin/pepper_view.h"
49 #include "remoting/client/software_video_renderer.h"
50 #include "remoting/client/token_fetcher_proxy.h"
51 #include "remoting/protocol/connection_to_host.h"
52 #include "remoting/protocol/host_stub.h"
53 #include "remoting/protocol/libjingle_transport_factory.h"
54 #include "third_party/webrtc/base/helpers.h"
55 #include "third_party/webrtc/base/ssladapter.h"
58 // Windows defines 'PostMessage', so we have to undef it.
59 #if defined(PostMessage)
67 // Default DPI to assume for old clients that use notifyClientResolution.
68 const int kDefaultDPI
= 96;
70 // Interval at which to sample performance statistics.
71 const int kPerfStatsIntervalMs
= 1000;
73 // URL scheme used by Chrome apps and extensions.
74 const char kChromeExtensionUrlScheme
[] = "chrome-extension";
76 #if defined(USE_OPENSSL)
77 // Size of the random seed blob used to initialize RNG in libjingle. Libjingle
78 // uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
79 // entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
80 // 1039 bytes of state, so we initialize it with 1k or random data.
81 const int kRandomSeedSize
= 1024;
82 #endif // defined(USE_OPENSSL)
84 std::string
ConnectionStateToString(protocol::ConnectionToHost::State state
) {
85 // Values returned by this function must match the
86 // remoting.ClientSession.State enum in JS code.
88 case protocol::ConnectionToHost::INITIALIZING
:
89 return "INITIALIZING";
90 case protocol::ConnectionToHost::CONNECTING
:
92 case protocol::ConnectionToHost::AUTHENTICATED
:
93 // Report the authenticated state as 'CONNECTING' to avoid changing
94 // the interface between the plugin and webapp.
96 case protocol::ConnectionToHost::CONNECTED
:
98 case protocol::ConnectionToHost::CLOSED
:
100 case protocol::ConnectionToHost::FAILED
:
104 return std::string();
107 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
108 // and let it handle it, but it would be hard to fix it now because
109 // client plugin and webapp versions may not be in sync. It should be
110 // easy to do after we are finished moving the client plugin to NaCl.
111 std::string
ConnectionErrorToString(protocol::ErrorCode error
) {
112 // Values returned by this function must match the
113 // remoting.ClientSession.Error enum in JS code.
118 case protocol::PEER_IS_OFFLINE
:
119 return "HOST_IS_OFFLINE";
121 case protocol::SESSION_REJECTED
:
122 case protocol::AUTHENTICATION_FAILED
:
123 return "SESSION_REJECTED";
125 case protocol::INCOMPATIBLE_PROTOCOL
:
126 return "INCOMPATIBLE_PROTOCOL";
128 case protocol::HOST_OVERLOAD
:
129 return "HOST_OVERLOAD";
131 case protocol::CHANNEL_CONNECTION_ERROR
:
132 case protocol::SIGNALING_ERROR
:
133 case protocol::SIGNALING_TIMEOUT
:
134 case protocol::UNKNOWN_ERROR
:
135 return "NETWORK_FAILURE";
137 DLOG(FATAL
) << "Unknown error code" << error
;
138 return std::string();
141 bool ParseAuthMethods(
142 const std::string
& auth_methods_str
,
143 std::vector
<protocol::AuthenticationMethod
>* auth_methods
) {
144 std::vector
<std::string
> parts
;
145 base::SplitString(auth_methods_str
, ',', &parts
);
146 for (std::vector
<std::string
>::iterator it
= parts
.begin();
147 it
!= parts
.end(); ++it
) {
148 protocol::AuthenticationMethod authentication_method
=
149 protocol::AuthenticationMethod::FromString(*it
);
150 if (authentication_method
.is_valid())
151 auth_methods
->push_back(authentication_method
);
153 if (auth_methods
->empty()) {
154 LOG(ERROR
) << "No valid authentication methods specified.";
161 // This flag blocks LOGs to the UI if we're already in the middle of logging
162 // to the UI. This prevents a potential infinite loop if we encounter an error
163 // while sending the log message to the UI.
164 bool g_logging_to_plugin
= false;
165 bool g_has_logging_instance
= false;
166 base::LazyInstance
<scoped_refptr
<base::SingleThreadTaskRunner
> >::Leaky
167 g_logging_task_runner
= LAZY_INSTANCE_INITIALIZER
;
168 base::LazyInstance
<base::WeakPtr
<ChromotingInstance
> >::Leaky
169 g_logging_instance
= LAZY_INSTANCE_INITIALIZER
;
170 base::LazyInstance
<base::Lock
>::Leaky
171 g_logging_lock
= LAZY_INSTANCE_INITIALIZER
;
172 logging::LogMessageHandlerFunction g_logging_old_handler
= NULL
;
176 // String sent in the "hello" message to the webapp to describe features.
177 const char ChromotingInstance::kApiFeatures
[] =
178 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
179 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
180 "pinlessAuth extensionMessage allowMouseLock mediaSourceRendering "
183 const char ChromotingInstance::kRequestedCapabilities
[] = "";
184 const char ChromotingInstance::kSupportedCapabilities
[] = "desktopShape";
186 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
187 : pp::Instance(pp_instance
),
189 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_
)),
190 context_(plugin_task_runner_
.get()),
191 input_tracker_(&mouse_input_filter_
),
192 key_mapper_(&input_tracker_
),
193 cursor_setter_(this),
194 empty_cursor_filter_(&cursor_setter_
),
195 text_input_controller_(this),
196 use_async_pin_dialog_(false),
197 use_media_source_rendering_(false),
198 weak_factory_(this) {
200 // In NaCl global resources need to be initialized differently because they
201 // are not shared with Chrome.
202 thread_task_runner_handle_
.reset(
203 new base::ThreadTaskRunnerHandle(plugin_task_runner_
));
204 thread_wrapper_
.reset(
205 new jingle_glue::JingleThreadWrapper(plugin_task_runner_
));
206 media::InitializeCPUSpecificYUVConversions();
208 // Register a global log handler.
209 ChromotingInstance::RegisterLogMessageHandler();
211 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
215 nacl_io_init_ppapi(pp_instance
, pp::Module::Get()->get_browser_interface());
216 mount("", "/etc", "memfs", 0, "");
217 mount("", "/usr", "memfs", 0, "");
220 // Register for mouse, wheel and keyboard events.
221 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
| PP_INPUTEVENT_CLASS_WHEEL
);
222 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD
);
224 // Disable the client-side IME in Chrome.
225 text_input_controller_
.SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
227 // Resister this instance to handle debug log messsages.
228 RegisterLoggingInstance();
230 #if defined(USE_OPENSSL)
231 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
232 char random_seed
[kRandomSeedSize
];
233 crypto::RandBytes(random_seed
, sizeof(random_seed
));
234 rtc::InitRandom(random_seed
, sizeof(random_seed
));
236 // Libjingle's SSL implementation is not really used, but it has to be
237 // initialized for NSS builds to make sure that RNG is initialized in NSS,
238 // because libjingle uses it.
239 rtc::InitializeSSL();
240 #endif // !defined(USE_OPENSSL)
242 // Send hello message.
243 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
244 data
->SetInteger("apiVersion", kApiVersion
);
245 data
->SetString("apiFeatures", kApiFeatures
);
246 data
->SetInteger("apiMinVersion", kApiMinMessagingVersion
);
247 data
->SetString("requestedCapabilities", kRequestedCapabilities
);
248 data
->SetString("supportedCapabilities", kSupportedCapabilities
);
250 PostLegacyJsonMessage("hello", data
.Pass());
253 ChromotingInstance::~ChromotingInstance() {
254 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
256 // Unregister this instance so that debug log messages will no longer be sent
257 // to it. This will stop all logging in all Chromoting instances.
258 UnregisterLoggingInstance();
260 // PepperView must be destroyed before the client.
261 view_weak_factory_
.reset();
266 plugin_task_runner_
->Quit();
268 // Ensure that nothing touches the plugin thread delegate after this point.
269 plugin_task_runner_
->DetachAndRunShutdownLoop();
271 // Stopping the context shuts down all chromoting threads.
275 bool ChromotingInstance::Init(uint32_t argc
,
277 const char* argv
[]) {
278 CHECK(!initialized_
);
281 VLOG(1) << "Started ChromotingInstance::Init";
283 // Check that the calling content is part of an app or extension. This is only
284 // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
285 // work in NaCl at the moment so the check fails in NaCl builds.
286 #if !defined(OS_NACL)
287 if (!IsCallerAppOrExtension()) {
288 LOG(ERROR
) << "Not an app or extension";
293 // Start all the threads.
299 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
300 if (!message
.is_string()) {
301 LOG(ERROR
) << "Received a message that is not a string.";
305 scoped_ptr
<base::Value
> json(
306 base::JSONReader::Read(message
.AsString(),
307 base::JSON_ALLOW_TRAILING_COMMAS
));
308 base::DictionaryValue
* message_dict
= NULL
;
310 base::DictionaryValue
* data
= NULL
;
312 !json
->GetAsDictionary(&message_dict
) ||
313 !message_dict
->GetString("method", &method
) ||
314 !message_dict
->GetDictionary("data", &data
)) {
315 LOG(ERROR
) << "Received invalid message:" << message
.AsString();
319 if (method
== "connect") {
320 HandleConnect(*data
);
321 } else if (method
== "disconnect") {
322 HandleDisconnect(*data
);
323 } else if (method
== "incomingIq") {
324 HandleOnIncomingIq(*data
);
325 } else if (method
== "releaseAllKeys") {
326 HandleReleaseAllKeys(*data
);
327 } else if (method
== "injectKeyEvent") {
328 HandleInjectKeyEvent(*data
);
329 } else if (method
== "remapKey") {
330 HandleRemapKey(*data
);
331 } else if (method
== "trapKey") {
332 HandleTrapKey(*data
);
333 } else if (method
== "sendClipboardItem") {
334 HandleSendClipboardItem(*data
);
335 } else if (method
== "notifyClientResolution") {
336 HandleNotifyClientResolution(*data
);
337 } else if (method
== "pauseVideo") {
338 HandlePauseVideo(*data
);
339 } else if (method
== "videoControl") {
340 HandleVideoControl(*data
);
341 } else if (method
== "pauseAudio") {
342 HandlePauseAudio(*data
);
343 } else if (method
== "useAsyncPinDialog") {
344 use_async_pin_dialog_
= true;
345 } else if (method
== "onPinFetched") {
346 HandleOnPinFetched(*data
);
347 } else if (method
== "onThirdPartyTokenFetched") {
348 HandleOnThirdPartyTokenFetched(*data
);
349 } else if (method
== "requestPairing") {
350 HandleRequestPairing(*data
);
351 } else if (method
== "extensionMessage") {
352 HandleExtensionMessage(*data
);
353 } else if (method
== "allowMouseLock") {
354 HandleAllowMouseLockMessage();
355 } else if (method
== "enableMediaSourceRendering") {
356 HandleEnableMediaSourceRendering();
357 } else if (method
== "sendMouseInputWhenUnfocused") {
358 HandleSendMouseInputWhenUnfocused();
359 } else if (method
== "delegateLargeCursors") {
360 HandleDelegateLargeCursors();
364 void ChromotingInstance::DidChangeFocus(bool has_focus
) {
365 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
370 input_handler_
.DidChangeFocus(has_focus
);
372 mouse_locker_
->DidChangeFocus(has_focus
);
375 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
376 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
379 mouse_input_filter_
.set_input_size(
380 webrtc::DesktopSize(view
.GetRect().width(), view
.GetRect().height()));
383 view_
->SetView(view
);
386 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
387 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
392 return input_handler_
.HandleInputEvent(event
);
395 void ChromotingInstance::SetDesktopSize(const webrtc::DesktopSize
& size
,
396 const webrtc::DesktopVector
& dpi
) {
397 mouse_input_filter_
.set_output_size(size
);
399 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
400 data
->SetInteger("width", size
.width());
401 data
->SetInteger("height", size
.height());
403 data
->SetInteger("x_dpi", dpi
.x());
405 data
->SetInteger("y_dpi", dpi
.y());
406 PostLegacyJsonMessage("onDesktopSize", data
.Pass());
409 void ChromotingInstance::SetDesktopShape(const webrtc::DesktopRegion
& shape
) {
410 if (desktop_shape_
&& shape
.Equals(*desktop_shape_
))
413 desktop_shape_
.reset(new webrtc::DesktopRegion(shape
));
415 scoped_ptr
<base::ListValue
> rects_value(new base::ListValue());
416 for (webrtc::DesktopRegion::Iterator
i(shape
); !i
.IsAtEnd(); i
.Advance()) {
417 const webrtc::DesktopRect
& rect
= i
.rect();
418 scoped_ptr
<base::ListValue
> rect_value(new base::ListValue());
419 rect_value
->AppendInteger(rect
.left());
420 rect_value
->AppendInteger(rect
.top());
421 rect_value
->AppendInteger(rect
.width());
422 rect_value
->AppendInteger(rect
.height());
423 rects_value
->Append(rect_value
.release());
426 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
427 data
->Set("rects", rects_value
.release());
428 PostLegacyJsonMessage("onDesktopShape", data
.Pass());
431 void ChromotingInstance::OnConnectionState(
432 protocol::ConnectionToHost::State state
,
433 protocol::ErrorCode error
) {
434 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
435 data
->SetString("state", ConnectionStateToString(state
));
436 data
->SetString("error", ConnectionErrorToString(error
));
437 PostLegacyJsonMessage("onConnectionStatus", data
.Pass());
440 void ChromotingInstance::FetchThirdPartyToken(
441 const GURL
& token_url
,
442 const std::string
& host_public_key
,
443 const std::string
& scope
,
444 base::WeakPtr
<TokenFetcherProxy
> token_fetcher_proxy
) {
445 // Once the Session object calls this function, it won't continue the
446 // authentication until the callback is called (or connection is canceled).
447 // So, it's impossible to reach this with a callback already registered.
448 DCHECK(!token_fetcher_proxy_
.get());
449 token_fetcher_proxy_
= token_fetcher_proxy
;
450 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
451 data
->SetString("tokenUrl", token_url
.spec());
452 data
->SetString("hostPublicKey", host_public_key
);
453 data
->SetString("scope", scope
);
454 PostLegacyJsonMessage("fetchThirdPartyToken", data
.Pass());
457 void ChromotingInstance::OnConnectionReady(bool ready
) {
458 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
459 data
->SetBoolean("ready", ready
);
460 PostLegacyJsonMessage("onConnectionReady", data
.Pass());
463 void ChromotingInstance::OnRouteChanged(const std::string
& channel_name
,
464 const protocol::TransportRoute
& route
) {
465 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
466 data
->SetString("channel", channel_name
);
467 data
->SetString("connectionType",
468 protocol::TransportRoute::GetTypeString(route
.type
));
469 PostLegacyJsonMessage("onRouteChanged", data
.Pass());
472 void ChromotingInstance::SetCapabilities(const std::string
& capabilities
) {
473 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
474 data
->SetString("capabilities", capabilities
);
475 PostLegacyJsonMessage("setCapabilities", data
.Pass());
478 void ChromotingInstance::SetPairingResponse(
479 const protocol::PairingResponse
& pairing_response
) {
480 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
481 data
->SetString("clientId", pairing_response
.client_id());
482 data
->SetString("sharedSecret", pairing_response
.shared_secret());
483 PostLegacyJsonMessage("pairingResponse", data
.Pass());
486 void ChromotingInstance::DeliverHostMessage(
487 const protocol::ExtensionMessage
& message
) {
488 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
489 data
->SetString("type", message
.type());
490 data
->SetString("data", message
.data());
491 PostLegacyJsonMessage("extensionMessage", data
.Pass());
494 void ChromotingInstance::FetchSecretFromDialog(
495 bool pairing_supported
,
496 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
497 // Once the Session object calls this function, it won't continue the
498 // authentication until the callback is called (or connection is canceled).
499 // So, it's impossible to reach this with a callback already registered.
500 DCHECK(secret_fetched_callback_
.is_null());
501 secret_fetched_callback_
= secret_fetched_callback
;
502 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
503 data
->SetBoolean("pairingSupported", pairing_supported
);
504 PostLegacyJsonMessage("fetchPin", data
.Pass());
507 void ChromotingInstance::FetchSecretFromString(
508 const std::string
& shared_secret
,
509 bool pairing_supported
,
510 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
511 secret_fetched_callback
.Run(shared_secret
);
514 protocol::ClipboardStub
* ChromotingInstance::GetClipboardStub() {
515 // TODO(sergeyu): Move clipboard handling to a separate class.
520 protocol::CursorShapeStub
* ChromotingInstance::GetCursorShapeStub() {
521 return &empty_cursor_filter_
;
524 void ChromotingInstance::InjectClipboardEvent(
525 const protocol::ClipboardEvent
& event
) {
526 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
527 data
->SetString("mimeType", event
.mime_type());
528 data
->SetString("item", event
.data());
529 PostLegacyJsonMessage("injectClipboardItem", data
.Pass());
532 void ChromotingInstance::SetCursorShape(
533 const protocol::CursorShapeInfo
& cursor_shape
) {
534 // If the delegated cursor is empty then stop rendering a DOM cursor.
535 if (IsCursorShapeEmpty(cursor_shape
)) {
536 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
540 // Cursor is not empty, so pass it to JS to render.
541 const int kBytesPerPixel
= sizeof(uint32_t);
542 const size_t buffer_size
=
543 cursor_shape
.height() * cursor_shape
.width() * kBytesPerPixel
;
545 pp::VarArrayBuffer
array_buffer(buffer_size
);
546 void* dst
= array_buffer
.Map();
547 memcpy(dst
, cursor_shape
.data().data(), buffer_size
);
548 array_buffer
.Unmap();
550 pp::VarDictionary dictionary
;
551 dictionary
.Set(pp::Var("width"), cursor_shape
.width());
552 dictionary
.Set(pp::Var("height"), cursor_shape
.height());
553 dictionary
.Set(pp::Var("hotspotX"), cursor_shape
.hotspot_x());
554 dictionary
.Set(pp::Var("hotspotY"), cursor_shape
.hotspot_y());
555 dictionary
.Set(pp::Var("data"), array_buffer
);
556 PostChromotingMessage("setCursorShape", dictionary
);
559 void ChromotingInstance::OnFirstFrameReceived() {
560 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
561 PostLegacyJsonMessage("onFirstFrameReceived", data
.Pass());
564 void ChromotingInstance::HandleConnect(const base::DictionaryValue
& data
) {
565 std::string local_jid
;
566 std::string host_jid
;
567 std::string host_public_key
;
568 std::string auth_methods_str
;
569 std::string authentication_tag
;
570 std::vector
<protocol::AuthenticationMethod
> auth_methods
;
571 if (!data
.GetString("hostJid", &host_jid
) ||
572 !data
.GetString("hostPublicKey", &host_public_key
) ||
573 !data
.GetString("localJid", &local_jid
) ||
574 !data
.GetString("authenticationMethods", &auth_methods_str
) ||
575 !ParseAuthMethods(auth_methods_str
, &auth_methods
) ||
576 !data
.GetString("authenticationTag", &authentication_tag
)) {
577 LOG(ERROR
) << "Invalid connect() data.";
581 std::string client_pairing_id
;
582 data
.GetString("clientPairingId", &client_pairing_id
);
583 std::string client_paired_secret
;
584 data
.GetString("clientPairedSecret", &client_paired_secret
);
586 protocol::FetchSecretCallback fetch_secret_callback
;
587 if (use_async_pin_dialog_
) {
588 fetch_secret_callback
= base::Bind(
589 &ChromotingInstance::FetchSecretFromDialog
, weak_factory_
.GetWeakPtr());
591 std::string shared_secret
;
592 if (!data
.GetString("sharedSecret", &shared_secret
)) {
593 LOG(ERROR
) << "sharedSecret not specified in connect().";
596 fetch_secret_callback
=
597 base::Bind(&ChromotingInstance::FetchSecretFromString
, shared_secret
);
600 // Read the list of capabilities, if any.
601 std::string capabilities
;
602 if (data
.HasKey("capabilities")) {
603 if (!data
.GetString("capabilities", &capabilities
)) {
604 LOG(ERROR
) << "Invalid connect() data.";
609 VLOG(0) << "Connecting to " << host_jid
610 << ". Local jid: " << local_jid
<< ".";
613 std::string key_filter
;
614 if (!data
.GetString("keyFilter", &key_filter
)) {
616 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
617 } else if (key_filter
== "mac") {
618 normalizing_input_filter_
.reset(
619 new NormalizingInputFilterMac(&key_mapper_
));
620 } else if (key_filter
== "cros") {
621 normalizing_input_filter_
.reset(
622 new NormalizingInputFilterCros(&key_mapper_
));
624 DCHECK(key_filter
.empty());
625 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
627 #elif defined(OS_MACOSX)
628 normalizing_input_filter_
.reset(new NormalizingInputFilterMac(&key_mapper_
));
629 #elif defined(OS_CHROMEOS)
630 normalizing_input_filter_
.reset(new NormalizingInputFilterCros(&key_mapper_
));
632 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
634 input_handler_
.set_input_stub(normalizing_input_filter_
.get());
636 if (use_media_source_rendering_
) {
637 video_renderer_
.reset(new MediaSourceVideoRenderer(this));
639 view_
.reset(new PepperView(this, &context_
));
640 view_weak_factory_
.reset(
641 new base::WeakPtrFactory
<FrameConsumer
>(view_
.get()));
643 // SoftwareVideoRenderer runs on a separate thread so for now we wrap
644 // PepperView with a ref-counted proxy object.
645 scoped_refptr
<FrameConsumerProxy
> consumer_proxy
=
646 new FrameConsumerProxy(plugin_task_runner_
,
647 view_weak_factory_
->GetWeakPtr());
649 SoftwareVideoRenderer
* renderer
=
650 new SoftwareVideoRenderer(context_
.main_task_runner(),
651 context_
.decode_task_runner(),
653 view_
->Initialize(renderer
);
654 if (!plugin_view_
.is_null())
655 view_
->SetView(plugin_view_
);
656 video_renderer_
.reset(renderer
);
659 scoped_ptr
<AudioPlayer
> audio_player(new PepperAudioPlayer(this));
660 client_
.reset(new ChromotingClient(&context_
, this, video_renderer_
.get(),
661 audio_player
.Pass()));
663 // Connect the input pipeline to the protocol stub & initialize components.
664 mouse_input_filter_
.set_input_stub(client_
->input_stub());
665 if (!plugin_view_
.is_null()) {
666 mouse_input_filter_
.set_input_size(webrtc::DesktopSize(
667 plugin_view_
.GetRect().width(), plugin_view_
.GetRect().height()));
670 // Setup the signal strategy.
671 signal_strategy_
.reset(new DelegatingSignalStrategy(
672 local_jid
, base::Bind(&ChromotingInstance::SendOutgoingIq
,
673 weak_factory_
.GetWeakPtr())));
675 // Create TransportFactory.
676 scoped_ptr
<protocol::TransportFactory
> transport_factory(
677 new protocol::LibjingleTransportFactory(
678 signal_strategy_
.get(),
679 PepperPortAllocator::Create(this).Pass(),
680 protocol::NetworkSettings(
681 protocol::NetworkSettings::NAT_TRAVERSAL_FULL
)));
683 // Create Authenticator.
684 scoped_ptr
<protocol::ThirdPartyClientAuthenticator::TokenFetcher
>
685 token_fetcher(new TokenFetcherProxy(
686 base::Bind(&ChromotingInstance::FetchThirdPartyToken
,
687 weak_factory_
.GetWeakPtr()),
689 scoped_ptr
<protocol::Authenticator
> authenticator(
690 new protocol::NegotiatingClientAuthenticator(
691 client_pairing_id
, client_paired_secret
, authentication_tag
,
692 fetch_secret_callback
, token_fetcher
.Pass(), auth_methods
));
694 // Kick off the connection.
695 client_
->Start(signal_strategy_
.get(), authenticator
.Pass(),
696 transport_factory
.Pass(), host_jid
, capabilities
);
698 // Start timer that periodically sends perf stats.
699 plugin_task_runner_
->PostDelayedTask(
700 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
701 weak_factory_
.GetWeakPtr()),
702 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
705 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue
& data
) {
706 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
708 // PepperView must be destroyed before the client.
709 view_weak_factory_
.reset();
712 VLOG(0) << "Disconnecting from host.";
714 // Disconnect the input pipeline and teardown the connection.
715 mouse_input_filter_
.set_input_stub(NULL
);
719 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue
& data
) {
721 if (!data
.GetString("iq", &iq
)) {
722 LOG(ERROR
) << "Invalid incomingIq() data.";
726 // Just ignore the message if it's received before Connect() is called. It's
727 // likely to be a leftover from a previous session, so it's safe to ignore it.
728 if (signal_strategy_
)
729 signal_strategy_
->OnIncomingMessage(iq
);
732 void ChromotingInstance::HandleReleaseAllKeys(
733 const base::DictionaryValue
& data
) {
735 input_tracker_
.ReleaseAll();
738 void ChromotingInstance::HandleInjectKeyEvent(
739 const base::DictionaryValue
& data
) {
741 bool is_pressed
= false;
742 if (!data
.GetInteger("usbKeycode", &usb_keycode
) ||
743 !data
.GetBoolean("pressed", &is_pressed
)) {
744 LOG(ERROR
) << "Invalid injectKeyEvent.";
748 protocol::KeyEvent event
;
749 event
.set_usb_keycode(usb_keycode
);
750 event
.set_pressed(is_pressed
);
752 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
754 input_tracker_
.InjectKeyEvent(event
);
757 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue
& data
) {
758 int from_keycode
= 0;
760 if (!data
.GetInteger("fromKeycode", &from_keycode
) ||
761 !data
.GetInteger("toKeycode", &to_keycode
)) {
762 LOG(ERROR
) << "Invalid remapKey.";
766 key_mapper_
.RemapKey(from_keycode
, to_keycode
);
769 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue
& data
) {
772 if (!data
.GetInteger("keycode", &keycode
) ||
773 !data
.GetBoolean("trap", &trap
)) {
774 LOG(ERROR
) << "Invalid trapKey.";
778 key_mapper_
.TrapKey(keycode
, trap
);
781 void ChromotingInstance::HandleSendClipboardItem(
782 const base::DictionaryValue
& data
) {
783 std::string mime_type
;
785 if (!data
.GetString("mimeType", &mime_type
) ||
786 !data
.GetString("item", &item
)) {
787 LOG(ERROR
) << "Invalid sendClipboardItem data.";
790 if (!IsConnected()) {
793 protocol::ClipboardEvent event
;
794 event
.set_mime_type(mime_type
);
795 event
.set_data(item
);
796 client_
->clipboard_forwarder()->InjectClipboardEvent(event
);
799 void ChromotingInstance::HandleNotifyClientResolution(
800 const base::DictionaryValue
& data
) {
803 int x_dpi
= kDefaultDPI
;
804 int y_dpi
= kDefaultDPI
;
805 if (!data
.GetInteger("width", &width
) ||
806 !data
.GetInteger("height", &height
) ||
807 !data
.GetInteger("x_dpi", &x_dpi
) ||
808 !data
.GetInteger("y_dpi", &y_dpi
) ||
809 width
<= 0 || height
<= 0 ||
810 x_dpi
<= 0 || y_dpi
<= 0) {
811 LOG(ERROR
) << "Invalid notifyClientResolution.";
815 if (!IsConnected()) {
819 protocol::ClientResolution client_resolution
;
820 client_resolution
.set_width(width
);
821 client_resolution
.set_height(height
);
822 client_resolution
.set_x_dpi(x_dpi
);
823 client_resolution
.set_y_dpi(y_dpi
);
825 // Include the legacy width & height in DIPs for use by older hosts.
826 client_resolution
.set_dips_width((width
* kDefaultDPI
) / x_dpi
);
827 client_resolution
.set_dips_height((height
* kDefaultDPI
) / y_dpi
);
829 client_
->host_stub()->NotifyClientResolution(client_resolution
);
832 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue
& data
) {
833 if (!data
.HasKey("pause")) {
834 LOG(ERROR
) << "Invalid pauseVideo.";
837 HandleVideoControl(data
);
840 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue
& data
) {
841 protocol::VideoControl video_control
;
842 bool pause_video
= false;
843 if (data
.GetBoolean("pause", &pause_video
)) {
844 video_control
.set_enable(!pause_video
);
846 bool lossless_encode
= false;
847 if (data
.GetBoolean("losslessEncode", &lossless_encode
)) {
848 video_control
.set_lossless_encode(lossless_encode
);
850 bool lossless_color
= false;
851 if (data
.GetBoolean("losslessColor", &lossless_color
)) {
852 video_control
.set_lossless_color(lossless_color
);
854 if (!IsConnected()) {
857 client_
->host_stub()->ControlVideo(video_control
);
860 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue
& data
) {
862 if (!data
.GetBoolean("pause", &pause
)) {
863 LOG(ERROR
) << "Invalid pauseAudio.";
866 if (!IsConnected()) {
869 protocol::AudioControl audio_control
;
870 audio_control
.set_enable(!pause
);
871 client_
->host_stub()->ControlAudio(audio_control
);
873 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue
& data
) {
875 if (!data
.GetString("pin", &pin
)) {
876 LOG(ERROR
) << "Invalid onPinFetched.";
879 if (!secret_fetched_callback_
.is_null()) {
880 secret_fetched_callback_
.Run(pin
);
881 secret_fetched_callback_
.Reset();
883 LOG(WARNING
) << "Ignored OnPinFetched received without a pending fetch.";
887 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
888 const base::DictionaryValue
& data
) {
890 std::string shared_secret
;
891 if (!data
.GetString("token", &token
) ||
892 !data
.GetString("sharedSecret", &shared_secret
)) {
893 LOG(ERROR
) << "Invalid onThirdPartyTokenFetched data.";
896 if (token_fetcher_proxy_
.get()) {
897 token_fetcher_proxy_
->OnTokenFetched(token
, shared_secret
);
898 token_fetcher_proxy_
.reset();
900 LOG(WARNING
) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
904 void ChromotingInstance::HandleRequestPairing(
905 const base::DictionaryValue
& data
) {
906 std::string client_name
;
907 if (!data
.GetString("clientName", &client_name
)) {
908 LOG(ERROR
) << "Invalid requestPairing";
911 if (!IsConnected()) {
914 protocol::PairingRequest pairing_request
;
915 pairing_request
.set_client_name(client_name
);
916 client_
->host_stub()->RequestPairing(pairing_request
);
919 void ChromotingInstance::HandleExtensionMessage(
920 const base::DictionaryValue
& data
) {
922 std::string message_data
;
923 if (!data
.GetString("type", &type
) ||
924 !data
.GetString("data", &message_data
)) {
925 LOG(ERROR
) << "Invalid extensionMessage.";
928 if (!IsConnected()) {
931 protocol::ExtensionMessage message
;
932 message
.set_type(type
);
933 message
.set_data(message_data
);
934 client_
->host_stub()->DeliverClientMessage(message
);
937 void ChromotingInstance::HandleAllowMouseLockMessage() {
938 // Create the mouse lock handler and route cursor shape messages through it.
939 mouse_locker_
.reset(new PepperMouseLocker(
940 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas
,
941 base::Unretained(&input_handler_
)),
943 empty_cursor_filter_
.set_cursor_stub(mouse_locker_
.get());
946 void ChromotingInstance::HandleEnableMediaSourceRendering() {
947 use_media_source_rendering_
= true;
950 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
951 input_handler_
.set_send_mouse_input_when_unfocused(true);
954 void ChromotingInstance::HandleDelegateLargeCursors() {
955 cursor_setter_
.set_delegate_stub(this);
958 ChromotingStats
* ChromotingInstance::GetStats() {
959 if (!video_renderer_
.get())
961 return video_renderer_
->GetStats();
964 void ChromotingInstance::PostChromotingMessage(const std::string
& method
,
965 const pp::VarDictionary
& data
) {
966 pp::VarDictionary message
;
967 message
.Set(pp::Var("method"), pp::Var(method
));
968 message
.Set(pp::Var("data"), data
);
969 PostMessage(message
);
972 void ChromotingInstance::PostLegacyJsonMessage(
973 const std::string
& method
,
974 scoped_ptr
<base::DictionaryValue
> data
) {
975 scoped_ptr
<base::DictionaryValue
> message(new base::DictionaryValue());
976 message
->SetString("method", method
);
977 message
->Set("data", data
.release());
979 std::string message_json
;
980 base::JSONWriter::Write(message
.get(), &message_json
);
981 PostMessage(pp::Var(message_json
));
984 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode
, bool pressed
) {
985 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
986 data
->SetInteger("usbKeycode", usb_keycode
);
987 data
->SetBoolean("pressed", pressed
);
988 PostLegacyJsonMessage("trappedKeyEvent", data
.Pass());
991 void ChromotingInstance::SendOutgoingIq(const std::string
& iq
) {
992 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
993 data
->SetString("iq", iq
);
994 PostLegacyJsonMessage("sendOutgoingIq", data
.Pass());
997 void ChromotingInstance::SendPerfStats() {
998 if (!video_renderer_
.get()) {
1002 plugin_task_runner_
->PostDelayedTask(
1003 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
1004 weak_factory_
.GetWeakPtr()),
1005 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
1007 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1008 ChromotingStats
* stats
= video_renderer_
->GetStats();
1009 data
->SetDouble("videoBandwidth", stats
->video_bandwidth()->Rate());
1010 data
->SetDouble("videoFrameRate", stats
->video_frame_rate()->Rate());
1011 data
->SetDouble("captureLatency", stats
->video_capture_ms()->Average());
1012 data
->SetDouble("encodeLatency", stats
->video_encode_ms()->Average());
1013 data
->SetDouble("decodeLatency", stats
->video_decode_ms()->Average());
1014 data
->SetDouble("renderLatency", stats
->video_paint_ms()->Average());
1015 data
->SetDouble("roundtripLatency", stats
->round_trip_ms()->Average());
1016 PostLegacyJsonMessage("onPerfStats", data
.Pass());
1020 void ChromotingInstance::RegisterLogMessageHandler() {
1021 base::AutoLock
lock(g_logging_lock
.Get());
1023 VLOG(1) << "Registering global log handler";
1025 // Record previous handler so we can call it in a chain.
1026 g_logging_old_handler
= logging::GetLogMessageHandler();
1028 // Set up log message handler.
1029 // This is not thread-safe so we need it within our lock.
1030 logging::SetLogMessageHandler(&LogToUI
);
1033 void ChromotingInstance::RegisterLoggingInstance() {
1034 base::AutoLock
lock(g_logging_lock
.Get());
1036 // Register this instance as the one that will handle all logging calls
1037 // and display them to the user.
1038 // If multiple plugins are run, then the last one registered will handle all
1039 // logging for all instances.
1040 g_logging_instance
.Get() = weak_factory_
.GetWeakPtr();
1041 g_logging_task_runner
.Get() = plugin_task_runner_
;
1042 g_has_logging_instance
= true;
1045 void ChromotingInstance::UnregisterLoggingInstance() {
1046 base::AutoLock
lock(g_logging_lock
.Get());
1048 // Don't unregister unless we're the currently registered instance.
1049 if (this != g_logging_instance
.Get().get())
1052 // Unregister this instance for logging.
1053 g_has_logging_instance
= false;
1054 g_logging_instance
.Get().reset();
1055 g_logging_task_runner
.Get() = NULL
;
1057 VLOG(1) << "Unregistering global log handler";
1061 bool ChromotingInstance::LogToUI(int severity
, const char* file
, int line
,
1062 size_t message_start
,
1063 const std::string
& str
) {
1064 // Note that we're reading |g_has_logging_instance| outside of a lock.
1065 // This lockless read is done so that we don't needlessly slow down global
1066 // logging with a lock for each log message.
1068 // This lockless read is safe because:
1070 // Misreading a false value (when it should be true) means that we'll simply
1071 // skip processing a few log messages.
1073 // Misreading a true value (when it should be false) means that we'll take
1074 // the lock and check |g_logging_instance| unnecessarily. This is not
1075 // problematic because we always set |g_logging_instance| inside a lock.
1076 if (g_has_logging_instance
) {
1077 scoped_refptr
<base::SingleThreadTaskRunner
> logging_task_runner
;
1078 base::WeakPtr
<ChromotingInstance
> logging_instance
;
1081 base::AutoLock
lock(g_logging_lock
.Get());
1082 // If we're on the logging thread and |g_logging_to_plugin| is set then
1083 // this LOG message came from handling a previous LOG message and we
1084 // should skip it to avoid an infinite loop of LOG messages.
1085 if (!g_logging_task_runner
.Get()->BelongsToCurrentThread() ||
1086 !g_logging_to_plugin
) {
1087 logging_task_runner
= g_logging_task_runner
.Get();
1088 logging_instance
= g_logging_instance
.Get();
1092 if (logging_task_runner
.get()) {
1093 std::string message
= remoting::GetTimestampString();
1094 message
+= (str
.c_str() + message_start
);
1096 logging_task_runner
->PostTask(
1097 FROM_HERE
, base::Bind(&ChromotingInstance::ProcessLogToUI
,
1098 logging_instance
, message
));
1102 if (g_logging_old_handler
)
1103 return (g_logging_old_handler
)(severity
, file
, line
, message_start
, str
);
1107 void ChromotingInstance::ProcessLogToUI(const std::string
& message
) {
1108 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1110 // This flag (which is set only here) is used to prevent LogToUI from posting
1111 // new tasks while we're in the middle of servicing a LOG call. This can
1112 // happen if the call to LogDebugInfo tries to LOG anything.
1113 // Since it is read on the plugin thread, we don't need to lock to set it.
1114 g_logging_to_plugin
= true;
1115 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1116 data
->SetString("message", message
);
1117 PostLegacyJsonMessage("logDebugMessage", data
.Pass());
1118 g_logging_to_plugin
= false;
1121 bool ChromotingInstance::IsCallerAppOrExtension() {
1122 const pp::URLUtil_Dev
* url_util
= pp::URLUtil_Dev::Get();
1126 PP_URLComponents_Dev url_components
;
1127 pp::Var url_var
= url_util
->GetDocumentURL(this, &url_components
);
1128 if (!url_var
.is_string())
1131 std::string url
= url_var
.AsString();
1132 std::string url_scheme
= url
.substr(url_components
.scheme
.begin
,
1133 url_components
.scheme
.len
);
1134 return url_scheme
== kChromeExtensionUrlScheme
;
1137 bool ChromotingInstance::IsConnected() {
1139 (client_
->connection_state() == protocol::ConnectionToHost::CONNECTED
);
1142 void ChromotingInstance::OnMediaSourceSize(const webrtc::DesktopSize
& size
,
1143 const webrtc::DesktopVector
& dpi
) {
1144 SetDesktopSize(size
, dpi
);
1147 void ChromotingInstance::OnMediaSourceShape(
1148 const webrtc::DesktopRegion
& shape
) {
1149 SetDesktopShape(shape
);
1152 void ChromotingInstance::OnMediaSourceReset(const std::string
& format
) {
1153 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1154 data
->SetString("format", format
);
1155 PostLegacyJsonMessage("mediaSourceReset", data
.Pass());
1158 void ChromotingInstance::OnMediaSourceData(uint8_t* buffer
, size_t buffer_size
,
1160 pp::VarArrayBuffer
array_buffer(buffer_size
);
1161 void* data_ptr
= array_buffer
.Map();
1162 memcpy(data_ptr
, buffer
, buffer_size
);
1163 array_buffer
.Unmap();
1164 pp::VarDictionary data_dictionary
;
1165 data_dictionary
.Set(pp::Var("buffer"), array_buffer
);
1166 data_dictionary
.Set(pp::Var("keyframe"), keyframe
);
1167 PostChromotingMessage("mediaSourceData", data_dictionary
);
1170 } // namespace remoting