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/callback_helpers.h"
18 #include "base/json/json_reader.h"
19 #include "base/json/json_writer.h"
20 #include "base/lazy_instance.h"
21 #include "base/logging.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/synchronization/lock.h"
25 #include "base/threading/thread.h"
26 #include "base/values.h"
27 #include "crypto/random.h"
28 #include "jingle/glue/thread_wrapper.h"
29 #include "media/base/yuv_convert.h"
30 #include "net/socket/ssl_server_socket.h"
31 #include "ppapi/cpp/completion_callback.h"
32 #include "ppapi/cpp/dev/url_util_dev.h"
33 #include "ppapi/cpp/image_data.h"
34 #include "ppapi/cpp/input_event.h"
35 #include "ppapi/cpp/rect.h"
36 #include "ppapi/cpp/var_array_buffer.h"
37 #include "ppapi/cpp/var_dictionary.h"
38 #include "remoting/base/constants.h"
39 #include "remoting/base/util.h"
40 #include "remoting/client/chromoting_client.h"
41 #include "remoting/client/plugin/delegating_signal_strategy.h"
42 #include "remoting/client/plugin/normalizing_input_filter_cros.h"
43 #include "remoting/client/plugin/normalizing_input_filter_mac.h"
44 #include "remoting/client/plugin/pepper_audio_player.h"
45 #include "remoting/client/plugin/pepper_mouse_locker.h"
46 #include "remoting/client/plugin/pepper_port_allocator.h"
47 #include "remoting/client/plugin/pepper_video_renderer_2d.h"
48 #include "remoting/client/plugin/pepper_video_renderer_3d.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 return "AUTHENTICATED";
94 case protocol::ConnectionToHost::CONNECTED
:
96 case protocol::ConnectionToHost::CLOSED
:
98 case protocol::ConnectionToHost::FAILED
:
102 return std::string();
105 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
106 // and let it handle it, but it would be hard to fix it now because
107 // client plugin and webapp versions may not be in sync. It should be
108 // easy to do after we are finished moving the client plugin to NaCl.
109 std::string
ConnectionErrorToString(protocol::ErrorCode error
) {
110 // Values returned by this function must match the
111 // remoting.ClientSession.Error enum in JS code.
116 case protocol::PEER_IS_OFFLINE
:
117 return "HOST_IS_OFFLINE";
119 case protocol::SESSION_REJECTED
:
120 case protocol::AUTHENTICATION_FAILED
:
121 return "SESSION_REJECTED";
123 case protocol::INCOMPATIBLE_PROTOCOL
:
124 return "INCOMPATIBLE_PROTOCOL";
126 case protocol::HOST_OVERLOAD
:
127 return "HOST_OVERLOAD";
129 case protocol::CHANNEL_CONNECTION_ERROR
:
130 case protocol::SIGNALING_ERROR
:
131 case protocol::SIGNALING_TIMEOUT
:
132 case protocol::UNKNOWN_ERROR
:
133 return "NETWORK_FAILURE";
135 DLOG(FATAL
) << "Unknown error code" << error
;
136 return std::string();
139 bool ParseAuthMethods(
140 const std::string
& auth_methods_str
,
141 std::vector
<protocol::AuthenticationMethod
>* auth_methods
) {
142 std::vector
<std::string
> parts
;
143 base::SplitString(auth_methods_str
, ',', &parts
);
144 for (std::vector
<std::string
>::iterator it
= parts
.begin();
145 it
!= parts
.end(); ++it
) {
146 protocol::AuthenticationMethod authentication_method
=
147 protocol::AuthenticationMethod::FromString(*it
);
148 if (authentication_method
.is_valid())
149 auth_methods
->push_back(authentication_method
);
151 if (auth_methods
->empty()) {
152 LOG(ERROR
) << "No valid authentication methods specified.";
159 // This flag blocks LOGs to the UI if we're already in the middle of logging
160 // to the UI. This prevents a potential infinite loop if we encounter an error
161 // while sending the log message to the UI.
162 bool g_logging_to_plugin
= false;
163 bool g_has_logging_instance
= false;
164 base::LazyInstance
<scoped_refptr
<base::SingleThreadTaskRunner
> >::Leaky
165 g_logging_task_runner
= LAZY_INSTANCE_INITIALIZER
;
166 base::LazyInstance
<base::WeakPtr
<ChromotingInstance
> >::Leaky
167 g_logging_instance
= LAZY_INSTANCE_INITIALIZER
;
168 base::LazyInstance
<base::Lock
>::Leaky
169 g_logging_lock
= LAZY_INSTANCE_INITIALIZER
;
170 logging::LogMessageHandlerFunction g_logging_old_handler
= nullptr;
174 // String sent in the "hello" message to the webapp to describe features.
175 const char ChromotingInstance::kApiFeatures
[] =
176 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
177 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
178 "pinlessAuth extensionMessage allowMouseLock videoControl";
180 const char ChromotingInstance::kRequestedCapabilities
[] = "";
181 const char ChromotingInstance::kSupportedCapabilities
[] = "desktopShape";
183 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance
)
184 : pp::Instance(pp_instance
),
186 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_
)),
187 context_(plugin_task_runner_
.get()),
188 input_tracker_(&mouse_input_filter_
),
189 touch_input_scaler_(&input_tracker_
),
190 key_mapper_(&touch_input_scaler_
),
191 input_handler_(&input_tracker_
),
192 cursor_setter_(this),
193 empty_cursor_filter_(&cursor_setter_
),
194 text_input_controller_(this),
195 use_async_pin_dialog_(false),
196 weak_factory_(this) {
198 // In NaCl global resources need to be initialized differently because they
199 // are not shared with Chrome.
200 thread_task_runner_handle_
.reset(
201 new base::ThreadTaskRunnerHandle(plugin_task_runner_
));
203 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_
);
204 media::InitializeCPUSpecificYUVConversions();
206 // Register a global log handler.
207 ChromotingInstance::RegisterLogMessageHandler();
209 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
213 nacl_io_init_ppapi(pp_instance
, pp::Module::Get()->get_browser_interface());
214 mount("", "/etc", "memfs", 0, "");
215 mount("", "/usr", "memfs", 0, "");
218 // Register for mouse, wheel and keyboard events.
219 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
| PP_INPUTEVENT_CLASS_WHEEL
);
220 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD
);
222 // Disable the client-side IME in Chrome.
223 text_input_controller_
.SetTextInputType(PP_TEXTINPUT_TYPE_NONE
);
225 // Resister this instance to handle debug log messsages.
226 RegisterLoggingInstance();
228 #if defined(USE_OPENSSL)
229 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
230 char random_seed
[kRandomSeedSize
];
231 crypto::RandBytes(random_seed
, sizeof(random_seed
));
232 rtc::InitRandom(random_seed
, sizeof(random_seed
));
234 // Libjingle's SSL implementation is not really used, but it has to be
235 // initialized for NSS builds to make sure that RNG is initialized in NSS,
236 // because libjingle uses it.
237 rtc::InitializeSSL();
238 #endif // !defined(USE_OPENSSL)
240 // Send hello message.
241 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
242 data
->SetInteger("apiVersion", kApiVersion
);
243 data
->SetString("apiFeatures", kApiFeatures
);
244 data
->SetInteger("apiMinVersion", kApiMinMessagingVersion
);
245 data
->SetString("requestedCapabilities", kRequestedCapabilities
);
246 data
->SetString("supportedCapabilities", kSupportedCapabilities
);
248 PostLegacyJsonMessage("hello", data
.Pass());
251 ChromotingInstance::~ChromotingInstance() {
252 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
254 // Disconnect the client.
257 // Unregister this instance so that debug log messages will no longer be sent
258 // to it. This will stop all logging in all Chromoting instances.
259 UnregisterLoggingInstance();
261 plugin_task_runner_
->Quit();
263 // Ensure that nothing touches the plugin thread delegate after this point.
264 plugin_task_runner_
->DetachAndRunShutdownLoop();
266 // Stopping the context shuts down all chromoting threads.
270 bool ChromotingInstance::Init(uint32_t argc
,
272 const char* argv
[]) {
273 CHECK(!initialized_
);
276 VLOG(1) << "Started ChromotingInstance::Init";
278 // Check that the calling content is part of an app or extension. This is only
279 // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
280 // work in NaCl at the moment so the check fails in NaCl builds.
281 #if !defined(OS_NACL)
282 if (!IsCallerAppOrExtension()) {
283 LOG(ERROR
) << "Not an app or extension";
288 // Start all the threads.
294 void ChromotingInstance::HandleMessage(const pp::Var
& message
) {
295 if (!message
.is_string()) {
296 LOG(ERROR
) << "Received a message that is not a string.";
300 scoped_ptr
<base::Value
> json(base::JSONReader::DeprecatedRead(
301 message
.AsString(), base::JSON_ALLOW_TRAILING_COMMAS
));
302 base::DictionaryValue
* message_dict
= nullptr;
304 base::DictionaryValue
* data
= nullptr;
306 !json
->GetAsDictionary(&message_dict
) ||
307 !message_dict
->GetString("method", &method
) ||
308 !message_dict
->GetDictionary("data", &data
)) {
309 LOG(ERROR
) << "Received invalid message:" << message
.AsString();
313 if (method
== "connect") {
314 HandleConnect(*data
);
315 } else if (method
== "disconnect") {
316 HandleDisconnect(*data
);
317 } else if (method
== "incomingIq") {
318 HandleOnIncomingIq(*data
);
319 } else if (method
== "releaseAllKeys") {
320 HandleReleaseAllKeys(*data
);
321 } else if (method
== "injectKeyEvent") {
322 HandleInjectKeyEvent(*data
);
323 } else if (method
== "remapKey") {
324 HandleRemapKey(*data
);
325 } else if (method
== "trapKey") {
326 HandleTrapKey(*data
);
327 } else if (method
== "sendClipboardItem") {
328 HandleSendClipboardItem(*data
);
329 } else if (method
== "notifyClientResolution") {
330 HandleNotifyClientResolution(*data
);
331 } else if (method
== "pauseVideo") {
332 HandlePauseVideo(*data
);
333 } else if (method
== "videoControl") {
334 HandleVideoControl(*data
);
335 } else if (method
== "pauseAudio") {
336 HandlePauseAudio(*data
);
337 } else if (method
== "useAsyncPinDialog") {
338 use_async_pin_dialog_
= true;
339 } else if (method
== "onPinFetched") {
340 HandleOnPinFetched(*data
);
341 } else if (method
== "onThirdPartyTokenFetched") {
342 HandleOnThirdPartyTokenFetched(*data
);
343 } else if (method
== "requestPairing") {
344 HandleRequestPairing(*data
);
345 } else if (method
== "extensionMessage") {
346 HandleExtensionMessage(*data
);
347 } else if (method
== "allowMouseLock") {
348 HandleAllowMouseLockMessage();
349 } else if (method
== "sendMouseInputWhenUnfocused") {
350 HandleSendMouseInputWhenUnfocused();
351 } else if (method
== "delegateLargeCursors") {
352 HandleDelegateLargeCursors();
353 } else if (method
== "enableDebugRegion") {
354 HandleEnableDebugRegion(*data
);
355 } else if (method
== "enableTouchEvents") {
356 HandleEnableTouchEvents(*data
);
360 void ChromotingInstance::DidChangeFocus(bool has_focus
) {
361 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
366 input_handler_
.DidChangeFocus(has_focus
);
368 mouse_locker_
->DidChangeFocus(has_focus
);
371 void ChromotingInstance::DidChangeView(const pp::View
& view
) {
372 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
375 webrtc::DesktopSize
size(
376 webrtc::DesktopSize(view
.GetRect().width(), view
.GetRect().height()));
377 mouse_input_filter_
.set_input_size(size
);
378 touch_input_scaler_
.set_input_size(size
);
381 video_renderer_
->OnViewChanged(view
);
384 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent
& event
) {
385 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
390 return input_handler_
.HandleInputEvent(event
);
393 void ChromotingInstance::OnVideoDecodeError() {
396 // Assume that the decoder failure was caused by the host not encoding video
397 // correctly and report it as a protocol error.
398 // TODO(sergeyu): Consider using a different error code in case the decoder
399 // error was caused by some other problem.
400 OnConnectionState(protocol::ConnectionToHost::FAILED
,
401 protocol::INCOMPATIBLE_PROTOCOL
);
404 void ChromotingInstance::OnVideoFirstFrameReceived() {
405 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
406 PostLegacyJsonMessage("onFirstFrameReceived", data
.Pass());
409 void ChromotingInstance::OnVideoSize(const webrtc::DesktopSize
& size
,
410 const webrtc::DesktopVector
& dpi
) {
411 mouse_input_filter_
.set_output_size(size
);
412 touch_input_scaler_
.set_output_size(size
);
414 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
415 data
->SetInteger("width", size
.width());
416 data
->SetInteger("height", size
.height());
418 data
->SetInteger("x_dpi", dpi
.x());
420 data
->SetInteger("y_dpi", dpi
.y());
421 PostLegacyJsonMessage("onDesktopSize", data
.Pass());
424 void ChromotingInstance::OnVideoShape(const webrtc::DesktopRegion
& shape
) {
425 if (desktop_shape_
&& shape
.Equals(*desktop_shape_
))
428 desktop_shape_
.reset(new webrtc::DesktopRegion(shape
));
430 scoped_ptr
<base::ListValue
> rects_value(new base::ListValue());
431 for (webrtc::DesktopRegion::Iterator
i(shape
); !i
.IsAtEnd(); i
.Advance()) {
432 const webrtc::DesktopRect
& rect
= i
.rect();
433 scoped_ptr
<base::ListValue
> rect_value(new base::ListValue());
434 rect_value
->AppendInteger(rect
.left());
435 rect_value
->AppendInteger(rect
.top());
436 rect_value
->AppendInteger(rect
.width());
437 rect_value
->AppendInteger(rect
.height());
438 rects_value
->Append(rect_value
.release());
441 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
442 data
->Set("rects", rects_value
.release());
443 PostLegacyJsonMessage("onDesktopShape", data
.Pass());
446 void ChromotingInstance::OnVideoFrameDirtyRegion(
447 const webrtc::DesktopRegion
& dirty_region
) {
448 scoped_ptr
<base::ListValue
> rects_value(new base::ListValue());
449 for (webrtc::DesktopRegion::Iterator
i(dirty_region
); !i
.IsAtEnd();
451 const webrtc::DesktopRect
& rect
= i
.rect();
452 scoped_ptr
<base::ListValue
> rect_value(new base::ListValue());
453 rect_value
->AppendInteger(rect
.left());
454 rect_value
->AppendInteger(rect
.top());
455 rect_value
->AppendInteger(rect
.width());
456 rect_value
->AppendInteger(rect
.height());
457 rects_value
->Append(rect_value
.release());
460 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
461 data
->Set("rects", rects_value
.release());
462 PostLegacyJsonMessage("onDebugRegion", data
.Pass());
465 void ChromotingInstance::OnConnectionState(
466 protocol::ConnectionToHost::State state
,
467 protocol::ErrorCode error
) {
468 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
469 data
->SetString("state", ConnectionStateToString(state
));
470 data
->SetString("error", ConnectionErrorToString(error
));
471 PostLegacyJsonMessage("onConnectionStatus", data
.Pass());
474 void ChromotingInstance::FetchThirdPartyToken(
475 const GURL
& token_url
,
476 const std::string
& host_public_key
,
477 const std::string
& scope
,
478 base::WeakPtr
<TokenFetcherProxy
> token_fetcher_proxy
) {
479 // Once the Session object calls this function, it won't continue the
480 // authentication until the callback is called (or connection is canceled).
481 // So, it's impossible to reach this with a callback already registered.
482 DCHECK(!token_fetcher_proxy_
.get());
483 token_fetcher_proxy_
= token_fetcher_proxy
;
484 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
485 data
->SetString("tokenUrl", token_url
.spec());
486 data
->SetString("hostPublicKey", host_public_key
);
487 data
->SetString("scope", scope
);
488 PostLegacyJsonMessage("fetchThirdPartyToken", data
.Pass());
491 void ChromotingInstance::OnConnectionReady(bool ready
) {
492 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
493 data
->SetBoolean("ready", ready
);
494 PostLegacyJsonMessage("onConnectionReady", data
.Pass());
497 void ChromotingInstance::OnRouteChanged(const std::string
& channel_name
,
498 const protocol::TransportRoute
& route
) {
499 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
500 data
->SetString("channel", channel_name
);
501 data
->SetString("connectionType",
502 protocol::TransportRoute::GetTypeString(route
.type
));
503 PostLegacyJsonMessage("onRouteChanged", data
.Pass());
506 void ChromotingInstance::SetCapabilities(const std::string
& capabilities
) {
507 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
508 data
->SetString("capabilities", capabilities
);
509 PostLegacyJsonMessage("setCapabilities", data
.Pass());
512 void ChromotingInstance::SetPairingResponse(
513 const protocol::PairingResponse
& pairing_response
) {
514 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
515 data
->SetString("clientId", pairing_response
.client_id());
516 data
->SetString("sharedSecret", pairing_response
.shared_secret());
517 PostLegacyJsonMessage("pairingResponse", data
.Pass());
520 void ChromotingInstance::DeliverHostMessage(
521 const protocol::ExtensionMessage
& message
) {
522 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
523 data
->SetString("type", message
.type());
524 data
->SetString("data", message
.data());
525 PostLegacyJsonMessage("extensionMessage", data
.Pass());
528 void ChromotingInstance::FetchSecretFromDialog(
529 bool pairing_supported
,
530 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
531 // Once the Session object calls this function, it won't continue the
532 // authentication until the callback is called (or connection is canceled).
533 // So, it's impossible to reach this with a callback already registered.
534 DCHECK(secret_fetched_callback_
.is_null());
535 secret_fetched_callback_
= secret_fetched_callback
;
536 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
537 data
->SetBoolean("pairingSupported", pairing_supported
);
538 PostLegacyJsonMessage("fetchPin", data
.Pass());
541 void ChromotingInstance::FetchSecretFromString(
542 const std::string
& shared_secret
,
543 bool pairing_supported
,
544 const protocol::SecretFetchedCallback
& secret_fetched_callback
) {
545 secret_fetched_callback
.Run(shared_secret
);
548 protocol::ClipboardStub
* ChromotingInstance::GetClipboardStub() {
549 // TODO(sergeyu): Move clipboard handling to a separate class.
554 protocol::CursorShapeStub
* ChromotingInstance::GetCursorShapeStub() {
555 return &empty_cursor_filter_
;
558 void ChromotingInstance::InjectClipboardEvent(
559 const protocol::ClipboardEvent
& event
) {
560 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
561 data
->SetString("mimeType", event
.mime_type());
562 data
->SetString("item", event
.data());
563 PostLegacyJsonMessage("injectClipboardItem", data
.Pass());
566 void ChromotingInstance::SetCursorShape(
567 const protocol::CursorShapeInfo
& cursor_shape
) {
568 // If the delegated cursor is empty then stop rendering a DOM cursor.
569 if (IsCursorShapeEmpty(cursor_shape
)) {
570 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
574 // Cursor is not empty, so pass it to JS to render.
575 const int kBytesPerPixel
= sizeof(uint32_t);
576 const size_t buffer_size
=
577 cursor_shape
.height() * cursor_shape
.width() * kBytesPerPixel
;
579 pp::VarArrayBuffer
array_buffer(buffer_size
);
580 void* dst
= array_buffer
.Map();
581 memcpy(dst
, cursor_shape
.data().data(), buffer_size
);
582 array_buffer
.Unmap();
584 pp::VarDictionary dictionary
;
585 dictionary
.Set(pp::Var("width"), cursor_shape
.width());
586 dictionary
.Set(pp::Var("height"), cursor_shape
.height());
587 dictionary
.Set(pp::Var("hotspotX"), cursor_shape
.hotspot_x());
588 dictionary
.Set(pp::Var("hotspotY"), cursor_shape
.hotspot_y());
589 dictionary
.Set(pp::Var("data"), array_buffer
);
590 PostChromotingMessage("setCursorShape", dictionary
);
593 void ChromotingInstance::HandleConnect(const base::DictionaryValue
& data
) {
594 std::string local_jid
;
595 std::string host_jid
;
596 std::string host_public_key
;
597 std::string auth_methods_str
;
598 std::string authentication_tag
;
599 std::vector
<protocol::AuthenticationMethod
> auth_methods
;
600 if (!data
.GetString("hostJid", &host_jid
) ||
601 !data
.GetString("hostPublicKey", &host_public_key
) ||
602 !data
.GetString("localJid", &local_jid
) ||
603 !data
.GetString("authenticationMethods", &auth_methods_str
) ||
604 !ParseAuthMethods(auth_methods_str
, &auth_methods
) ||
605 !data
.GetString("authenticationTag", &authentication_tag
)) {
606 LOG(ERROR
) << "Invalid connect() data.";
610 std::string client_pairing_id
;
611 data
.GetString("clientPairingId", &client_pairing_id
);
612 std::string client_paired_secret
;
613 data
.GetString("clientPairedSecret", &client_paired_secret
);
615 protocol::FetchSecretCallback fetch_secret_callback
;
616 if (use_async_pin_dialog_
) {
617 fetch_secret_callback
= base::Bind(
618 &ChromotingInstance::FetchSecretFromDialog
, weak_factory_
.GetWeakPtr());
620 std::string shared_secret
;
621 if (!data
.GetString("sharedSecret", &shared_secret
)) {
622 LOG(ERROR
) << "sharedSecret not specified in connect().";
625 fetch_secret_callback
=
626 base::Bind(&ChromotingInstance::FetchSecretFromString
, shared_secret
);
629 // Read the list of capabilities, if any.
630 std::string capabilities
;
631 if (data
.HasKey("capabilities")) {
632 if (!data
.GetString("capabilities", &capabilities
)) {
633 LOG(ERROR
) << "Invalid connect() data.";
638 VLOG(0) << "Connecting to " << host_jid
639 << ". Local jid: " << local_jid
<< ".";
642 std::string key_filter
;
643 if (!data
.GetString("keyFilter", &key_filter
)) {
645 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
646 } else if (key_filter
== "mac") {
647 normalizing_input_filter_
.reset(
648 new NormalizingInputFilterMac(&key_mapper_
));
649 } else if (key_filter
== "cros") {
650 normalizing_input_filter_
.reset(
651 new NormalizingInputFilterCros(&key_mapper_
));
653 DCHECK(key_filter
.empty());
654 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
656 #elif defined(OS_MACOSX)
657 normalizing_input_filter_
.reset(new NormalizingInputFilterMac(&key_mapper_
));
658 #elif defined(OS_CHROMEOS)
659 normalizing_input_filter_
.reset(new NormalizingInputFilterCros(&key_mapper_
));
661 normalizing_input_filter_
.reset(new protocol::InputFilter(&key_mapper_
));
663 input_handler_
.set_input_stub(normalizing_input_filter_
.get());
665 // PPB_VideoDecoder is not always enabled because it's broken in some versions
666 // of Chrome. See crbug.com/447403 .
667 bool enable_video_decode_renderer
= false;
668 if (data
.GetBoolean("enableVideoDecodeRenderer",
669 &enable_video_decode_renderer
) &&
670 enable_video_decode_renderer
) {
671 LogToWebapp("Initializing 3D renderer.");
672 video_renderer_
.reset(new PepperVideoRenderer3D());
673 if (!video_renderer_
->Initialize(this, context_
, this))
674 video_renderer_
.reset();
677 // If we didn't initialize 3D renderer then use the 2D renderer.
678 if (!video_renderer_
) {
679 LogToWebapp("Initializing 2D renderer.");
680 video_renderer_
.reset(new PepperVideoRenderer2D());
681 if (!video_renderer_
->Initialize(this, context_
, this))
682 video_renderer_
.reset();
685 CHECK(video_renderer_
);
687 if (!plugin_view_
.is_null())
688 video_renderer_
->OnViewChanged(plugin_view_
);
690 scoped_ptr
<AudioPlayer
> audio_player(new PepperAudioPlayer(this));
691 client_
.reset(new ChromotingClient(&context_
, this, video_renderer_
.get(),
692 audio_player
.Pass()));
694 // Connect the input pipeline to the protocol stub & initialize components.
695 mouse_input_filter_
.set_input_stub(client_
->input_stub());
696 if (!plugin_view_
.is_null()) {
697 webrtc::DesktopSize
size(plugin_view_
.GetRect().width(),
698 plugin_view_
.GetRect().height());
699 mouse_input_filter_
.set_input_size(size
);
700 touch_input_scaler_
.set_input_size(size
);
703 // Setup the signal strategy.
704 signal_strategy_
.reset(new DelegatingSignalStrategy(
705 local_jid
, base::Bind(&ChromotingInstance::SendOutgoingIq
,
706 weak_factory_
.GetWeakPtr())));
708 // Create TransportFactory.
709 scoped_ptr
<protocol::TransportFactory
> transport_factory(
710 new protocol::LibjingleTransportFactory(
711 signal_strategy_
.get(), PepperPortAllocator::Create(this).Pass(),
712 protocol::NetworkSettings(
713 protocol::NetworkSettings::NAT_TRAVERSAL_FULL
),
714 protocol::TransportRole::CLIENT
));
716 // Create Authenticator.
717 scoped_ptr
<protocol::ThirdPartyClientAuthenticator::TokenFetcher
>
718 token_fetcher(new TokenFetcherProxy(
719 base::Bind(&ChromotingInstance::FetchThirdPartyToken
,
720 weak_factory_
.GetWeakPtr()),
722 scoped_ptr
<protocol::Authenticator
> authenticator(
723 new protocol::NegotiatingClientAuthenticator(
724 client_pairing_id
, client_paired_secret
, authentication_tag
,
725 fetch_secret_callback
, token_fetcher
.Pass(), auth_methods
));
727 // Kick off the connection.
728 client_
->Start(signal_strategy_
.get(), authenticator
.Pass(),
729 transport_factory
.Pass(), host_jid
, capabilities
);
731 // Start timer that periodically sends perf stats.
732 plugin_task_runner_
->PostDelayedTask(
733 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
734 weak_factory_
.GetWeakPtr()),
735 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
738 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue
& data
) {
739 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
743 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue
& data
) {
745 if (!data
.GetString("iq", &iq
)) {
746 LOG(ERROR
) << "Invalid incomingIq() data.";
750 // Just ignore the message if it's received before Connect() is called. It's
751 // likely to be a leftover from a previous session, so it's safe to ignore it.
752 if (signal_strategy_
)
753 signal_strategy_
->OnIncomingMessage(iq
);
756 void ChromotingInstance::HandleReleaseAllKeys(
757 const base::DictionaryValue
& data
) {
759 input_tracker_
.ReleaseAll();
762 void ChromotingInstance::HandleInjectKeyEvent(
763 const base::DictionaryValue
& data
) {
765 bool is_pressed
= false;
766 if (!data
.GetInteger("usbKeycode", &usb_keycode
) ||
767 !data
.GetBoolean("pressed", &is_pressed
)) {
768 LOG(ERROR
) << "Invalid injectKeyEvent.";
772 protocol::KeyEvent event
;
773 event
.set_usb_keycode(usb_keycode
);
774 event
.set_pressed(is_pressed
);
776 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
778 input_tracker_
.InjectKeyEvent(event
);
781 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue
& data
) {
782 int from_keycode
= 0;
784 if (!data
.GetInteger("fromKeycode", &from_keycode
) ||
785 !data
.GetInteger("toKeycode", &to_keycode
)) {
786 LOG(ERROR
) << "Invalid remapKey.";
790 key_mapper_
.RemapKey(from_keycode
, to_keycode
);
793 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue
& data
) {
796 if (!data
.GetInteger("keycode", &keycode
) ||
797 !data
.GetBoolean("trap", &trap
)) {
798 LOG(ERROR
) << "Invalid trapKey.";
802 key_mapper_
.TrapKey(keycode
, trap
);
805 void ChromotingInstance::HandleSendClipboardItem(
806 const base::DictionaryValue
& data
) {
807 std::string mime_type
;
809 if (!data
.GetString("mimeType", &mime_type
) ||
810 !data
.GetString("item", &item
)) {
811 LOG(ERROR
) << "Invalid sendClipboardItem data.";
814 if (!IsConnected()) {
817 protocol::ClipboardEvent event
;
818 event
.set_mime_type(mime_type
);
819 event
.set_data(item
);
820 client_
->clipboard_forwarder()->InjectClipboardEvent(event
);
823 void ChromotingInstance::HandleNotifyClientResolution(
824 const base::DictionaryValue
& data
) {
827 int x_dpi
= kDefaultDPI
;
828 int y_dpi
= kDefaultDPI
;
829 if (!data
.GetInteger("width", &width
) ||
830 !data
.GetInteger("height", &height
) ||
831 !data
.GetInteger("x_dpi", &x_dpi
) ||
832 !data
.GetInteger("y_dpi", &y_dpi
) ||
833 width
<= 0 || height
<= 0 ||
834 x_dpi
<= 0 || y_dpi
<= 0) {
835 LOG(ERROR
) << "Invalid notifyClientResolution.";
839 if (!IsConnected()) {
843 protocol::ClientResolution client_resolution
;
844 client_resolution
.set_width(width
);
845 client_resolution
.set_height(height
);
846 client_resolution
.set_x_dpi(x_dpi
);
847 client_resolution
.set_y_dpi(y_dpi
);
849 // Include the legacy width & height in DIPs for use by older hosts.
850 client_resolution
.set_dips_width((width
* kDefaultDPI
) / x_dpi
);
851 client_resolution
.set_dips_height((height
* kDefaultDPI
) / y_dpi
);
853 client_
->host_stub()->NotifyClientResolution(client_resolution
);
856 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue
& data
) {
857 if (!data
.HasKey("pause")) {
858 LOG(ERROR
) << "Invalid pauseVideo.";
861 HandleVideoControl(data
);
864 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue
& data
) {
865 protocol::VideoControl video_control
;
866 bool pause_video
= false;
867 if (data
.GetBoolean("pause", &pause_video
)) {
868 video_control
.set_enable(!pause_video
);
870 bool lossless_encode
= false;
871 if (data
.GetBoolean("losslessEncode", &lossless_encode
)) {
872 video_control
.set_lossless_encode(lossless_encode
);
874 bool lossless_color
= false;
875 if (data
.GetBoolean("losslessColor", &lossless_color
)) {
876 video_control
.set_lossless_color(lossless_color
);
878 if (!IsConnected()) {
881 client_
->host_stub()->ControlVideo(video_control
);
884 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue
& data
) {
886 if (!data
.GetBoolean("pause", &pause
)) {
887 LOG(ERROR
) << "Invalid pauseAudio.";
890 if (!IsConnected()) {
893 protocol::AudioControl audio_control
;
894 audio_control
.set_enable(!pause
);
895 client_
->host_stub()->ControlAudio(audio_control
);
897 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue
& data
) {
899 if (!data
.GetString("pin", &pin
)) {
900 LOG(ERROR
) << "Invalid onPinFetched.";
903 if (!secret_fetched_callback_
.is_null()) {
904 base::ResetAndReturn(&secret_fetched_callback_
).Run(pin
);
906 LOG(WARNING
) << "Ignored OnPinFetched received without a pending fetch.";
910 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
911 const base::DictionaryValue
& data
) {
913 std::string shared_secret
;
914 if (!data
.GetString("token", &token
) ||
915 !data
.GetString("sharedSecret", &shared_secret
)) {
916 LOG(ERROR
) << "Invalid onThirdPartyTokenFetched data.";
919 if (token_fetcher_proxy_
.get()) {
920 token_fetcher_proxy_
->OnTokenFetched(token
, shared_secret
);
921 token_fetcher_proxy_
.reset();
923 LOG(WARNING
) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
927 void ChromotingInstance::HandleRequestPairing(
928 const base::DictionaryValue
& data
) {
929 std::string client_name
;
930 if (!data
.GetString("clientName", &client_name
)) {
931 LOG(ERROR
) << "Invalid requestPairing";
934 if (!IsConnected()) {
937 protocol::PairingRequest pairing_request
;
938 pairing_request
.set_client_name(client_name
);
939 client_
->host_stub()->RequestPairing(pairing_request
);
942 void ChromotingInstance::HandleExtensionMessage(
943 const base::DictionaryValue
& data
) {
945 std::string message_data
;
946 if (!data
.GetString("type", &type
) ||
947 !data
.GetString("data", &message_data
)) {
948 LOG(ERROR
) << "Invalid extensionMessage.";
951 if (!IsConnected()) {
954 protocol::ExtensionMessage message
;
955 message
.set_type(type
);
956 message
.set_data(message_data
);
957 client_
->host_stub()->DeliverClientMessage(message
);
960 void ChromotingInstance::HandleAllowMouseLockMessage() {
961 // Create the mouse lock handler and route cursor shape messages through it.
962 mouse_locker_
.reset(new PepperMouseLocker(
963 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas
,
964 base::Unretained(&input_handler_
)),
966 empty_cursor_filter_
.set_cursor_stub(mouse_locker_
.get());
969 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
970 input_handler_
.set_send_mouse_input_when_unfocused(true);
973 void ChromotingInstance::HandleDelegateLargeCursors() {
974 cursor_setter_
.set_delegate_stub(this);
977 void ChromotingInstance::HandleEnableDebugRegion(
978 const base::DictionaryValue
& data
) {
980 if (!data
.GetBoolean("enable", &enable
)) {
981 LOG(ERROR
) << "Invalid enableDebugRegion.";
985 video_renderer_
->EnableDebugDirtyRegion(enable
);
988 void ChromotingInstance::HandleEnableTouchEvents(
989 const base::DictionaryValue
& data
) {
991 if (!data
.GetBoolean("enable", &enable
)) {
992 LOG(ERROR
) << "Invalid handleTouchEvents.";
997 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH
);
999 ClearInputEventRequest(PP_INPUTEVENT_CLASS_TOUCH
);
1003 void ChromotingInstance::Disconnect() {
1004 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1006 VLOG(0) << "Disconnecting from host.";
1008 // Disconnect the input pipeline and teardown the connection.
1009 mouse_input_filter_
.set_input_stub(nullptr);
1011 video_renderer_
.reset();
1014 void ChromotingInstance::PostChromotingMessage(const std::string
& method
,
1015 const pp::VarDictionary
& data
) {
1016 pp::VarDictionary message
;
1017 message
.Set(pp::Var("method"), pp::Var(method
));
1018 message
.Set(pp::Var("data"), data
);
1019 PostMessage(message
);
1022 void ChromotingInstance::PostLegacyJsonMessage(
1023 const std::string
& method
,
1024 scoped_ptr
<base::DictionaryValue
> data
) {
1025 base::DictionaryValue message
;
1026 message
.SetString("method", method
);
1027 message
.Set("data", data
.release());
1029 std::string message_json
;
1030 base::JSONWriter::Write(message
, &message_json
);
1031 PostMessage(pp::Var(message_json
));
1034 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode
, bool pressed
) {
1035 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1036 data
->SetInteger("usbKeycode", usb_keycode
);
1037 data
->SetBoolean("pressed", pressed
);
1038 PostLegacyJsonMessage("trappedKeyEvent", data
.Pass());
1041 void ChromotingInstance::SendOutgoingIq(const std::string
& iq
) {
1042 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1043 data
->SetString("iq", iq
);
1044 PostLegacyJsonMessage("sendOutgoingIq", data
.Pass());
1047 void ChromotingInstance::SendPerfStats() {
1048 if (!video_renderer_
.get()) {
1052 plugin_task_runner_
->PostDelayedTask(
1053 FROM_HERE
, base::Bind(&ChromotingInstance::SendPerfStats
,
1054 weak_factory_
.GetWeakPtr()),
1055 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs
));
1057 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1058 ChromotingStats
* stats
= video_renderer_
->GetStats();
1059 data
->SetDouble("videoBandwidth", stats
->video_bandwidth()->Rate());
1060 data
->SetDouble("videoFrameRate", stats
->video_frame_rate()->Rate());
1061 data
->SetDouble("captureLatency", stats
->video_capture_ms()->Average());
1062 data
->SetDouble("encodeLatency", stats
->video_encode_ms()->Average());
1063 data
->SetDouble("decodeLatency", stats
->video_decode_ms()->Average());
1064 data
->SetDouble("renderLatency", stats
->video_paint_ms()->Average());
1065 data
->SetDouble("roundtripLatency", stats
->round_trip_ms()->Average());
1066 PostLegacyJsonMessage("onPerfStats", data
.Pass());
1070 void ChromotingInstance::RegisterLogMessageHandler() {
1071 base::AutoLock
lock(g_logging_lock
.Get());
1073 VLOG(1) << "Registering global log handler";
1075 // Record previous handler so we can call it in a chain.
1076 g_logging_old_handler
= logging::GetLogMessageHandler();
1078 // Set up log message handler.
1079 // This is not thread-safe so we need it within our lock.
1080 logging::SetLogMessageHandler(&LogToUI
);
1083 void ChromotingInstance::RegisterLoggingInstance() {
1084 base::AutoLock
lock(g_logging_lock
.Get());
1086 // Register this instance as the one that will handle all logging calls
1087 // and display them to the user.
1088 // If multiple plugins are run, then the last one registered will handle all
1089 // logging for all instances.
1090 g_logging_instance
.Get() = weak_factory_
.GetWeakPtr();
1091 g_logging_task_runner
.Get() = plugin_task_runner_
;
1092 g_has_logging_instance
= true;
1095 void ChromotingInstance::UnregisterLoggingInstance() {
1096 base::AutoLock
lock(g_logging_lock
.Get());
1098 // Don't unregister unless we're the currently registered instance.
1099 if (this != g_logging_instance
.Get().get())
1102 // Unregister this instance for logging.
1103 g_has_logging_instance
= false;
1104 g_logging_instance
.Get().reset();
1105 g_logging_task_runner
.Get() = nullptr;
1107 VLOG(1) << "Unregistering global log handler";
1111 bool ChromotingInstance::LogToUI(int severity
, const char* file
, int line
,
1112 size_t message_start
,
1113 const std::string
& str
) {
1114 // Note that we're reading |g_has_logging_instance| outside of a lock.
1115 // This lockless read is done so that we don't needlessly slow down global
1116 // logging with a lock for each log message.
1118 // This lockless read is safe because:
1120 // Misreading a false value (when it should be true) means that we'll simply
1121 // skip processing a few log messages.
1123 // Misreading a true value (when it should be false) means that we'll take
1124 // the lock and check |g_logging_instance| unnecessarily. This is not
1125 // problematic because we always set |g_logging_instance| inside a lock.
1126 if (g_has_logging_instance
) {
1127 scoped_refptr
<base::SingleThreadTaskRunner
> logging_task_runner
;
1128 base::WeakPtr
<ChromotingInstance
> logging_instance
;
1131 base::AutoLock
lock(g_logging_lock
.Get());
1132 // If we're on the logging thread and |g_logging_to_plugin| is set then
1133 // this LOG message came from handling a previous LOG message and we
1134 // should skip it to avoid an infinite loop of LOG messages.
1135 if (!g_logging_task_runner
.Get()->BelongsToCurrentThread() ||
1136 !g_logging_to_plugin
) {
1137 logging_task_runner
= g_logging_task_runner
.Get();
1138 logging_instance
= g_logging_instance
.Get();
1142 if (logging_task_runner
.get()) {
1143 std::string message
= remoting::GetTimestampString();
1144 message
+= (str
.c_str() + message_start
);
1146 logging_task_runner
->PostTask(
1147 FROM_HERE
, base::Bind(&ChromotingInstance::ProcessLogToUI
,
1148 logging_instance
, message
));
1152 if (g_logging_old_handler
)
1153 return (g_logging_old_handler
)(severity
, file
, line
, message_start
, str
);
1157 void ChromotingInstance::ProcessLogToUI(const std::string
& message
) {
1158 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1160 // This flag (which is set only here) is used to prevent LogToUI from posting
1161 // new tasks while we're in the middle of servicing a LOG call. This can
1162 // happen if the call to LogDebugInfo tries to LOG anything.
1163 // Since it is read on the plugin thread, we don't need to lock to set it.
1164 g_logging_to_plugin
= true;
1165 scoped_ptr
<base::DictionaryValue
> data(new base::DictionaryValue());
1166 data
->SetString("message", message
);
1167 PostLegacyJsonMessage("logDebugMessage", data
.Pass());
1168 g_logging_to_plugin
= false;
1171 bool ChromotingInstance::IsCallerAppOrExtension() {
1172 const pp::URLUtil_Dev
* url_util
= pp::URLUtil_Dev::Get();
1176 PP_URLComponents_Dev url_components
;
1177 pp::Var url_var
= url_util
->GetDocumentURL(this, &url_components
);
1178 if (!url_var
.is_string())
1181 std::string url
= url_var
.AsString();
1182 std::string url_scheme
= url
.substr(url_components
.scheme
.begin
,
1183 url_components
.scheme
.len
);
1184 return url_scheme
== kChromeExtensionUrlScheme
;
1187 bool ChromotingInstance::IsConnected() {
1189 (client_
->connection_state() == protocol::ConnectionToHost::CONNECTED
);
1192 void ChromotingInstance::LogToWebapp(const std::string
& message
) {
1193 DCHECK(plugin_task_runner_
->BelongsToCurrentThread());
1195 LOG(ERROR
) << message
;
1197 #if !defined(OS_NACL)
1198 // Log messages are forwarded to the webapp only in PNaCl version of the
1199 // plugin, so ProcessLogToUI() needs to be called explicitly in the non-PNaCl
1201 ProcessLogToUI(message
);
1202 #endif // !defined(OS_NACL)
1205 } // namespace remoting