Re-land: C++ readability review
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blob262bbbd6853deb4b5033547052d3044b8fd9c9b3
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"
7 #include <string>
8 #include <vector>
10 #if defined(OS_NACL)
11 #include <nacl_io/nacl_io.h>
12 #include <sys/mount.h>
13 #endif
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/plugin/delegating_signal_strategy.h"
41 #include "remoting/client/plugin/normalizing_input_filter_cros.h"
42 #include "remoting/client/plugin/normalizing_input_filter_mac.h"
43 #include "remoting/client/plugin/pepper_audio_player.h"
44 #include "remoting/client/plugin/pepper_mouse_locker.h"
45 #include "remoting/client/plugin/pepper_port_allocator.h"
46 #include "remoting/client/plugin/pepper_video_renderer_2d.h"
47 #include "remoting/client/plugin/pepper_video_renderer_3d.h"
48 #include "remoting/client/software_video_renderer.h"
49 #include "remoting/client/token_fetcher_proxy.h"
50 #include "remoting/protocol/connection_to_host.h"
51 #include "remoting/protocol/host_stub.h"
52 #include "remoting/protocol/libjingle_transport_factory.h"
53 #include "third_party/webrtc/base/helpers.h"
54 #include "third_party/webrtc/base/ssladapter.h"
55 #include "url/gurl.h"
57 // Windows defines 'PostMessage', so we have to undef it.
58 #if defined(PostMessage)
59 #undef PostMessage
60 #endif
62 namespace remoting {
64 namespace {
66 // Default DPI to assume for old clients that use notifyClientResolution.
67 const int kDefaultDPI = 96;
69 // Interval at which to sample performance statistics.
70 const int kPerfStatsIntervalMs = 1000;
72 // URL scheme used by Chrome apps and extensions.
73 const char kChromeExtensionUrlScheme[] = "chrome-extension";
75 #if defined(USE_OPENSSL)
76 // Size of the random seed blob used to initialize RNG in libjingle. Libjingle
77 // uses the seed only for OpenSSL builds. OpenSSL needs at least 32 bytes of
78 // entropy (see http://wiki.openssl.org/index.php/Random_Numbers), but stores
79 // 1039 bytes of state, so we initialize it with 1k or random data.
80 const int kRandomSeedSize = 1024;
81 #endif // defined(USE_OPENSSL)
83 std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
84 // Values returned by this function must match the
85 // remoting.ClientSession.State enum in JS code.
86 switch (state) {
87 case protocol::ConnectionToHost::INITIALIZING:
88 return "INITIALIZING";
89 case protocol::ConnectionToHost::CONNECTING:
90 return "CONNECTING";
91 case protocol::ConnectionToHost::AUTHENTICATED:
92 // Report the authenticated state as 'CONNECTING' to avoid changing
93 // the interface between the plugin and webapp.
94 // TODO(garykac) Change to 'AUTHENTICATED' in M44 or once we've switched
95 // the client to NaCl.
96 return "CONNECTING";
97 case protocol::ConnectionToHost::CONNECTED:
98 return "CONNECTED";
99 case protocol::ConnectionToHost::CLOSED:
100 return "CLOSED";
101 case protocol::ConnectionToHost::FAILED:
102 return "FAILED";
104 NOTREACHED();
105 return std::string();
108 // TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
109 // and let it handle it, but it would be hard to fix it now because
110 // client plugin and webapp versions may not be in sync. It should be
111 // easy to do after we are finished moving the client plugin to NaCl.
112 std::string ConnectionErrorToString(protocol::ErrorCode error) {
113 // Values returned by this function must match the
114 // remoting.ClientSession.Error enum in JS code.
115 switch (error) {
116 case protocol::OK:
117 return "NONE";
119 case protocol::PEER_IS_OFFLINE:
120 return "HOST_IS_OFFLINE";
122 case protocol::SESSION_REJECTED:
123 case protocol::AUTHENTICATION_FAILED:
124 return "SESSION_REJECTED";
126 case protocol::INCOMPATIBLE_PROTOCOL:
127 return "INCOMPATIBLE_PROTOCOL";
129 case protocol::HOST_OVERLOAD:
130 return "HOST_OVERLOAD";
132 case protocol::CHANNEL_CONNECTION_ERROR:
133 case protocol::SIGNALING_ERROR:
134 case protocol::SIGNALING_TIMEOUT:
135 case protocol::UNKNOWN_ERROR:
136 return "NETWORK_FAILURE";
138 DLOG(FATAL) << "Unknown error code" << error;
139 return std::string();
142 bool ParseAuthMethods(
143 const std::string& auth_methods_str,
144 std::vector<protocol::AuthenticationMethod>* auth_methods) {
145 std::vector<std::string> parts;
146 base::SplitString(auth_methods_str, ',', &parts);
147 for (std::vector<std::string>::iterator it = parts.begin();
148 it != parts.end(); ++it) {
149 protocol::AuthenticationMethod authentication_method =
150 protocol::AuthenticationMethod::FromString(*it);
151 if (authentication_method.is_valid())
152 auth_methods->push_back(authentication_method);
154 if (auth_methods->empty()) {
155 LOG(ERROR) << "No valid authentication methods specified.";
156 return false;
159 return true;
162 // This flag blocks LOGs to the UI if we're already in the middle of logging
163 // to the UI. This prevents a potential infinite loop if we encounter an error
164 // while sending the log message to the UI.
165 bool g_logging_to_plugin = false;
166 bool g_has_logging_instance = false;
167 base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
168 g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
169 base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
170 g_logging_instance = LAZY_INSTANCE_INITIALIZER;
171 base::LazyInstance<base::Lock>::Leaky
172 g_logging_lock = LAZY_INSTANCE_INITIALIZER;
173 logging::LogMessageHandlerFunction g_logging_old_handler = nullptr;
175 } // namespace
177 // String sent in the "hello" message to the webapp to describe features.
178 const char ChromotingInstance::kApiFeatures[] =
179 "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
180 "notifyClientResolution pauseVideo pauseAudio asyncPin thirdPartyAuth "
181 "pinlessAuth extensionMessage allowMouseLock videoControl";
183 const char ChromotingInstance::kRequestedCapabilities[] = "";
184 const char ChromotingInstance::kSupportedCapabilities[] = "desktopShape";
186 ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
187 : pp::Instance(pp_instance),
188 initialized_(false),
189 plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)),
190 context_(plugin_task_runner_.get()),
191 input_tracker_(&mouse_input_filter_),
192 touch_input_scaler_(&input_tracker_),
193 key_mapper_(&touch_input_scaler_),
194 input_handler_(&input_tracker_),
195 cursor_setter_(this),
196 empty_cursor_filter_(&cursor_setter_),
197 text_input_controller_(this),
198 use_async_pin_dialog_(false),
199 weak_factory_(this) {
200 #if defined(OS_NACL)
201 // In NaCl global resources need to be initialized differently because they
202 // are not shared with Chrome.
203 thread_task_runner_handle_.reset(
204 new base::ThreadTaskRunnerHandle(plugin_task_runner_));
205 thread_wrapper_ =
206 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_);
207 media::InitializeCPUSpecificYUVConversions();
209 // Register a global log handler.
210 ChromotingInstance::RegisterLogMessageHandler();
211 #else
212 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
213 #endif
215 #if defined(OS_NACL)
216 nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
217 mount("", "/etc", "memfs", 0, "");
218 mount("", "/usr", "memfs", 0, "");
219 #endif
221 // Register for mouse, wheel and keyboard events.
222 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
223 RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
225 // Disable the client-side IME in Chrome.
226 text_input_controller_.SetTextInputType(PP_TEXTINPUT_TYPE_NONE);
228 // Resister this instance to handle debug log messsages.
229 RegisterLoggingInstance();
231 #if defined(USE_OPENSSL)
232 // Initialize random seed for libjingle. It's necessary only with OpenSSL.
233 char random_seed[kRandomSeedSize];
234 crypto::RandBytes(random_seed, sizeof(random_seed));
235 rtc::InitRandom(random_seed, sizeof(random_seed));
236 #else
237 // Libjingle's SSL implementation is not really used, but it has to be
238 // initialized for NSS builds to make sure that RNG is initialized in NSS,
239 // because libjingle uses it.
240 rtc::InitializeSSL();
241 #endif // !defined(USE_OPENSSL)
243 // Send hello message.
244 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
245 data->SetInteger("apiVersion", kApiVersion);
246 data->SetString("apiFeatures", kApiFeatures);
247 data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
248 data->SetString("requestedCapabilities", kRequestedCapabilities);
249 data->SetString("supportedCapabilities", kSupportedCapabilities);
251 PostLegacyJsonMessage("hello", data.Pass());
254 ChromotingInstance::~ChromotingInstance() {
255 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
257 // Disconnect the client.
258 Disconnect();
260 // Unregister this instance so that debug log messages will no longer be sent
261 // to it. This will stop all logging in all Chromoting instances.
262 UnregisterLoggingInstance();
264 plugin_task_runner_->Quit();
266 // Ensure that nothing touches the plugin thread delegate after this point.
267 plugin_task_runner_->DetachAndRunShutdownLoop();
269 // Stopping the context shuts down all chromoting threads.
270 context_.Stop();
273 bool ChromotingInstance::Init(uint32_t argc,
274 const char* argn[],
275 const char* argv[]) {
276 CHECK(!initialized_);
277 initialized_ = true;
279 VLOG(1) << "Started ChromotingInstance::Init";
281 // Check that the calling content is part of an app or extension. This is only
282 // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
283 // work in NaCl at the moment so the check fails in NaCl builds.
284 #if !defined(OS_NACL)
285 if (!IsCallerAppOrExtension()) {
286 LOG(ERROR) << "Not an app or extension";
287 return false;
289 #endif
291 // Start all the threads.
292 context_.Start();
294 return true;
297 void ChromotingInstance::HandleMessage(const pp::Var& message) {
298 if (!message.is_string()) {
299 LOG(ERROR) << "Received a message that is not a string.";
300 return;
303 scoped_ptr<base::Value> json(
304 base::JSONReader::Read(message.AsString(),
305 base::JSON_ALLOW_TRAILING_COMMAS));
306 base::DictionaryValue* message_dict = nullptr;
307 std::string method;
308 base::DictionaryValue* data = nullptr;
309 if (!json.get() ||
310 !json->GetAsDictionary(&message_dict) ||
311 !message_dict->GetString("method", &method) ||
312 !message_dict->GetDictionary("data", &data)) {
313 LOG(ERROR) << "Received invalid message:" << message.AsString();
314 return;
317 if (method == "connect") {
318 HandleConnect(*data);
319 } else if (method == "disconnect") {
320 HandleDisconnect(*data);
321 } else if (method == "incomingIq") {
322 HandleOnIncomingIq(*data);
323 } else if (method == "releaseAllKeys") {
324 HandleReleaseAllKeys(*data);
325 } else if (method == "injectKeyEvent") {
326 HandleInjectKeyEvent(*data);
327 } else if (method == "remapKey") {
328 HandleRemapKey(*data);
329 } else if (method == "trapKey") {
330 HandleTrapKey(*data);
331 } else if (method == "sendClipboardItem") {
332 HandleSendClipboardItem(*data);
333 } else if (method == "notifyClientResolution") {
334 HandleNotifyClientResolution(*data);
335 } else if (method == "pauseVideo") {
336 HandlePauseVideo(*data);
337 } else if (method == "videoControl") {
338 HandleVideoControl(*data);
339 } else if (method == "pauseAudio") {
340 HandlePauseAudio(*data);
341 } else if (method == "useAsyncPinDialog") {
342 use_async_pin_dialog_ = true;
343 } else if (method == "onPinFetched") {
344 HandleOnPinFetched(*data);
345 } else if (method == "onThirdPartyTokenFetched") {
346 HandleOnThirdPartyTokenFetched(*data);
347 } else if (method == "requestPairing") {
348 HandleRequestPairing(*data);
349 } else if (method == "extensionMessage") {
350 HandleExtensionMessage(*data);
351 } else if (method == "allowMouseLock") {
352 HandleAllowMouseLockMessage();
353 } else if (method == "sendMouseInputWhenUnfocused") {
354 HandleSendMouseInputWhenUnfocused();
355 } else if (method == "delegateLargeCursors") {
356 HandleDelegateLargeCursors();
357 } else if (method == "enableDebugRegion") {
358 HandleEnableDebugRegion(*data);
359 } else if (method == "enableTouchEvents") {
360 HandleEnableTouchEvents();
364 void ChromotingInstance::DidChangeFocus(bool has_focus) {
365 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
367 if (!IsConnected())
368 return;
370 input_handler_.DidChangeFocus(has_focus);
371 if (mouse_locker_)
372 mouse_locker_->DidChangeFocus(has_focus);
375 void ChromotingInstance::DidChangeView(const pp::View& view) {
376 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
378 plugin_view_ = view;
379 webrtc::DesktopSize size(
380 webrtc::DesktopSize(view.GetRect().width(), view.GetRect().height()));
381 mouse_input_filter_.set_input_size(size);
382 touch_input_scaler_.set_input_size(size);
384 if (video_renderer_)
385 video_renderer_->OnViewChanged(view);
388 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
389 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
391 if (!IsConnected())
392 return false;
394 return input_handler_.HandleInputEvent(event);
397 void ChromotingInstance::OnVideoDecodeError() {
398 Disconnect();
400 // Assume that the decoder failure was caused by the host not encoding video
401 // correctly and report it as a protocol error.
402 // TODO(sergeyu): Consider using a different error code in case the decoder
403 // error was caused by some other problem.
404 OnConnectionState(protocol::ConnectionToHost::FAILED,
405 protocol::INCOMPATIBLE_PROTOCOL);
408 void ChromotingInstance::OnVideoFirstFrameReceived() {
409 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
410 PostLegacyJsonMessage("onFirstFrameReceived", data.Pass());
413 void ChromotingInstance::OnVideoSize(const webrtc::DesktopSize& size,
414 const webrtc::DesktopVector& dpi) {
415 mouse_input_filter_.set_output_size(size);
416 touch_input_scaler_.set_output_size(size);
418 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
419 data->SetInteger("width", size.width());
420 data->SetInteger("height", size.height());
421 if (dpi.x())
422 data->SetInteger("x_dpi", dpi.x());
423 if (dpi.y())
424 data->SetInteger("y_dpi", dpi.y());
425 PostLegacyJsonMessage("onDesktopSize", data.Pass());
428 void ChromotingInstance::OnVideoShape(const webrtc::DesktopRegion& shape) {
429 if (desktop_shape_ && shape.Equals(*desktop_shape_))
430 return;
432 desktop_shape_.reset(new webrtc::DesktopRegion(shape));
434 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
435 for (webrtc::DesktopRegion::Iterator i(shape); !i.IsAtEnd(); i.Advance()) {
436 const webrtc::DesktopRect& rect = i.rect();
437 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
438 rect_value->AppendInteger(rect.left());
439 rect_value->AppendInteger(rect.top());
440 rect_value->AppendInteger(rect.width());
441 rect_value->AppendInteger(rect.height());
442 rects_value->Append(rect_value.release());
445 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
446 data->Set("rects", rects_value.release());
447 PostLegacyJsonMessage("onDesktopShape", data.Pass());
450 void ChromotingInstance::OnVideoFrameDirtyRegion(
451 const webrtc::DesktopRegion& dirty_region) {
452 scoped_ptr<base::ListValue> rects_value(new base::ListValue());
453 for (webrtc::DesktopRegion::Iterator i(dirty_region); !i.IsAtEnd();
454 i.Advance()) {
455 const webrtc::DesktopRect& rect = i.rect();
456 scoped_ptr<base::ListValue> rect_value(new base::ListValue());
457 rect_value->AppendInteger(rect.left());
458 rect_value->AppendInteger(rect.top());
459 rect_value->AppendInteger(rect.width());
460 rect_value->AppendInteger(rect.height());
461 rects_value->Append(rect_value.release());
464 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
465 data->Set("rects", rects_value.release());
466 PostLegacyJsonMessage("onDebugRegion", data.Pass());
469 void ChromotingInstance::OnConnectionState(
470 protocol::ConnectionToHost::State state,
471 protocol::ErrorCode error) {
472 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
473 data->SetString("state", ConnectionStateToString(state));
474 data->SetString("error", ConnectionErrorToString(error));
475 PostLegacyJsonMessage("onConnectionStatus", data.Pass());
478 void ChromotingInstance::FetchThirdPartyToken(
479 const GURL& token_url,
480 const std::string& host_public_key,
481 const std::string& scope,
482 base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
483 // Once the Session object calls this function, it won't continue the
484 // authentication until the callback is called (or connection is canceled).
485 // So, it's impossible to reach this with a callback already registered.
486 DCHECK(!token_fetcher_proxy_.get());
487 token_fetcher_proxy_ = token_fetcher_proxy;
488 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
489 data->SetString("tokenUrl", token_url.spec());
490 data->SetString("hostPublicKey", host_public_key);
491 data->SetString("scope", scope);
492 PostLegacyJsonMessage("fetchThirdPartyToken", data.Pass());
495 void ChromotingInstance::OnConnectionReady(bool ready) {
496 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
497 data->SetBoolean("ready", ready);
498 PostLegacyJsonMessage("onConnectionReady", data.Pass());
501 void ChromotingInstance::OnRouteChanged(const std::string& channel_name,
502 const protocol::TransportRoute& route) {
503 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
504 data->SetString("channel", channel_name);
505 data->SetString("connectionType",
506 protocol::TransportRoute::GetTypeString(route.type));
507 PostLegacyJsonMessage("onRouteChanged", data.Pass());
510 void ChromotingInstance::SetCapabilities(const std::string& capabilities) {
511 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
512 data->SetString("capabilities", capabilities);
513 PostLegacyJsonMessage("setCapabilities", data.Pass());
516 void ChromotingInstance::SetPairingResponse(
517 const protocol::PairingResponse& pairing_response) {
518 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
519 data->SetString("clientId", pairing_response.client_id());
520 data->SetString("sharedSecret", pairing_response.shared_secret());
521 PostLegacyJsonMessage("pairingResponse", data.Pass());
524 void ChromotingInstance::DeliverHostMessage(
525 const protocol::ExtensionMessage& message) {
526 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
527 data->SetString("type", message.type());
528 data->SetString("data", message.data());
529 PostLegacyJsonMessage("extensionMessage", data.Pass());
532 void ChromotingInstance::FetchSecretFromDialog(
533 bool pairing_supported,
534 const protocol::SecretFetchedCallback& secret_fetched_callback) {
535 // Once the Session object calls this function, it won't continue the
536 // authentication until the callback is called (or connection is canceled).
537 // So, it's impossible to reach this with a callback already registered.
538 DCHECK(secret_fetched_callback_.is_null());
539 secret_fetched_callback_ = secret_fetched_callback;
540 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
541 data->SetBoolean("pairingSupported", pairing_supported);
542 PostLegacyJsonMessage("fetchPin", data.Pass());
545 void ChromotingInstance::FetchSecretFromString(
546 const std::string& shared_secret,
547 bool pairing_supported,
548 const protocol::SecretFetchedCallback& secret_fetched_callback) {
549 secret_fetched_callback.Run(shared_secret);
552 protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
553 // TODO(sergeyu): Move clipboard handling to a separate class.
554 // crbug.com/138108
555 return this;
558 protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
559 return &empty_cursor_filter_;
562 void ChromotingInstance::InjectClipboardEvent(
563 const protocol::ClipboardEvent& event) {
564 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
565 data->SetString("mimeType", event.mime_type());
566 data->SetString("item", event.data());
567 PostLegacyJsonMessage("injectClipboardItem", data.Pass());
570 void ChromotingInstance::SetCursorShape(
571 const protocol::CursorShapeInfo& cursor_shape) {
572 // If the delegated cursor is empty then stop rendering a DOM cursor.
573 if (IsCursorShapeEmpty(cursor_shape)) {
574 PostChromotingMessage("unsetCursorShape", pp::VarDictionary());
575 return;
578 // Cursor is not empty, so pass it to JS to render.
579 const int kBytesPerPixel = sizeof(uint32_t);
580 const size_t buffer_size =
581 cursor_shape.height() * cursor_shape.width() * kBytesPerPixel;
583 pp::VarArrayBuffer array_buffer(buffer_size);
584 void* dst = array_buffer.Map();
585 memcpy(dst, cursor_shape.data().data(), buffer_size);
586 array_buffer.Unmap();
588 pp::VarDictionary dictionary;
589 dictionary.Set(pp::Var("width"), cursor_shape.width());
590 dictionary.Set(pp::Var("height"), cursor_shape.height());
591 dictionary.Set(pp::Var("hotspotX"), cursor_shape.hotspot_x());
592 dictionary.Set(pp::Var("hotspotY"), cursor_shape.hotspot_y());
593 dictionary.Set(pp::Var("data"), array_buffer);
594 PostChromotingMessage("setCursorShape", dictionary);
597 void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
598 std::string local_jid;
599 std::string host_jid;
600 std::string host_public_key;
601 std::string auth_methods_str;
602 std::string authentication_tag;
603 std::vector<protocol::AuthenticationMethod> auth_methods;
604 if (!data.GetString("hostJid", &host_jid) ||
605 !data.GetString("hostPublicKey", &host_public_key) ||
606 !data.GetString("localJid", &local_jid) ||
607 !data.GetString("authenticationMethods", &auth_methods_str) ||
608 !ParseAuthMethods(auth_methods_str, &auth_methods) ||
609 !data.GetString("authenticationTag", &authentication_tag)) {
610 LOG(ERROR) << "Invalid connect() data.";
611 return;
614 std::string client_pairing_id;
615 data.GetString("clientPairingId", &client_pairing_id);
616 std::string client_paired_secret;
617 data.GetString("clientPairedSecret", &client_paired_secret);
619 protocol::FetchSecretCallback fetch_secret_callback;
620 if (use_async_pin_dialog_) {
621 fetch_secret_callback = base::Bind(
622 &ChromotingInstance::FetchSecretFromDialog, weak_factory_.GetWeakPtr());
623 } else {
624 std::string shared_secret;
625 if (!data.GetString("sharedSecret", &shared_secret)) {
626 LOG(ERROR) << "sharedSecret not specified in connect().";
627 return;
629 fetch_secret_callback =
630 base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
633 // Read the list of capabilities, if any.
634 std::string capabilities;
635 if (data.HasKey("capabilities")) {
636 if (!data.GetString("capabilities", &capabilities)) {
637 LOG(ERROR) << "Invalid connect() data.";
638 return;
642 VLOG(0) << "Connecting to " << host_jid
643 << ". Local jid: " << local_jid << ".";
645 #if defined(OS_NACL)
646 std::string key_filter;
647 if (!data.GetString("keyFilter", &key_filter)) {
648 NOTREACHED();
649 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
650 } else if (key_filter == "mac") {
651 normalizing_input_filter_.reset(
652 new NormalizingInputFilterMac(&key_mapper_));
653 } else if (key_filter == "cros") {
654 normalizing_input_filter_.reset(
655 new NormalizingInputFilterCros(&key_mapper_));
656 } else {
657 DCHECK(key_filter.empty());
658 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
660 #elif defined(OS_MACOSX)
661 normalizing_input_filter_.reset(new NormalizingInputFilterMac(&key_mapper_));
662 #elif defined(OS_CHROMEOS)
663 normalizing_input_filter_.reset(new NormalizingInputFilterCros(&key_mapper_));
664 #else
665 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
666 #endif
667 input_handler_.set_input_stub(normalizing_input_filter_.get());
669 // PPB_VideoDecoder is not always enabled because it's broken in some versions
670 // of Chrome. See crbug.com/447403 .
671 bool enable_video_decode_renderer = false;
672 if (data.GetBoolean("enableVideoDecodeRenderer",
673 &enable_video_decode_renderer) &&
674 enable_video_decode_renderer) {
675 LogToWebapp("Initializing 3D renderer.");
676 video_renderer_.reset(new PepperVideoRenderer3D());
677 if (!video_renderer_->Initialize(this, context_, this))
678 video_renderer_.reset();
681 // If we didn't initialize 3D renderer then use the 2D renderer.
682 if (!video_renderer_) {
683 LogToWebapp("Initializing 2D renderer.");
684 video_renderer_.reset(new PepperVideoRenderer2D());
685 if (!video_renderer_->Initialize(this, context_, this))
686 video_renderer_.reset();
689 CHECK(video_renderer_);
691 if (!plugin_view_.is_null())
692 video_renderer_->OnViewChanged(plugin_view_);
694 scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
695 client_.reset(new ChromotingClient(&context_, this, video_renderer_.get(),
696 audio_player.Pass()));
698 // Connect the input pipeline to the protocol stub & initialize components.
699 mouse_input_filter_.set_input_stub(client_->input_stub());
700 if (!plugin_view_.is_null()) {
701 webrtc::DesktopSize size(plugin_view_.GetRect().width(),
702 plugin_view_.GetRect().height());
703 mouse_input_filter_.set_input_size(size);
704 touch_input_scaler_.set_input_size(size);
707 // Setup the signal strategy.
708 signal_strategy_.reset(new DelegatingSignalStrategy(
709 local_jid, base::Bind(&ChromotingInstance::SendOutgoingIq,
710 weak_factory_.GetWeakPtr())));
712 // Create TransportFactory.
713 scoped_ptr<protocol::TransportFactory> transport_factory(
714 new protocol::LibjingleTransportFactory(
715 signal_strategy_.get(),
716 PepperPortAllocator::Create(this).Pass(),
717 protocol::NetworkSettings(
718 protocol::NetworkSettings::NAT_TRAVERSAL_FULL)));
720 // Create Authenticator.
721 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
722 token_fetcher(new TokenFetcherProxy(
723 base::Bind(&ChromotingInstance::FetchThirdPartyToken,
724 weak_factory_.GetWeakPtr()),
725 host_public_key));
726 scoped_ptr<protocol::Authenticator> authenticator(
727 new protocol::NegotiatingClientAuthenticator(
728 client_pairing_id, client_paired_secret, authentication_tag,
729 fetch_secret_callback, token_fetcher.Pass(), auth_methods));
731 // Kick off the connection.
732 client_->Start(signal_strategy_.get(), authenticator.Pass(),
733 transport_factory.Pass(), host_jid, capabilities);
735 // Start timer that periodically sends perf stats.
736 plugin_task_runner_->PostDelayedTask(
737 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
738 weak_factory_.GetWeakPtr()),
739 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
742 void ChromotingInstance::HandleDisconnect(const base::DictionaryValue& data) {
743 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
744 Disconnect();
747 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
748 std::string iq;
749 if (!data.GetString("iq", &iq)) {
750 LOG(ERROR) << "Invalid incomingIq() data.";
751 return;
754 // Just ignore the message if it's received before Connect() is called. It's
755 // likely to be a leftover from a previous session, so it's safe to ignore it.
756 if (signal_strategy_)
757 signal_strategy_->OnIncomingMessage(iq);
760 void ChromotingInstance::HandleReleaseAllKeys(
761 const base::DictionaryValue& data) {
762 if (IsConnected())
763 input_tracker_.ReleaseAll();
766 void ChromotingInstance::HandleInjectKeyEvent(
767 const base::DictionaryValue& data) {
768 int usb_keycode = 0;
769 bool is_pressed = false;
770 if (!data.GetInteger("usbKeycode", &usb_keycode) ||
771 !data.GetBoolean("pressed", &is_pressed)) {
772 LOG(ERROR) << "Invalid injectKeyEvent.";
773 return;
776 protocol::KeyEvent event;
777 event.set_usb_keycode(usb_keycode);
778 event.set_pressed(is_pressed);
780 // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
781 if (IsConnected())
782 input_tracker_.InjectKeyEvent(event);
785 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
786 int from_keycode = 0;
787 int to_keycode = 0;
788 if (!data.GetInteger("fromKeycode", &from_keycode) ||
789 !data.GetInteger("toKeycode", &to_keycode)) {
790 LOG(ERROR) << "Invalid remapKey.";
791 return;
794 key_mapper_.RemapKey(from_keycode, to_keycode);
797 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
798 int keycode = 0;
799 bool trap = false;
800 if (!data.GetInteger("keycode", &keycode) ||
801 !data.GetBoolean("trap", &trap)) {
802 LOG(ERROR) << "Invalid trapKey.";
803 return;
806 key_mapper_.TrapKey(keycode, trap);
809 void ChromotingInstance::HandleSendClipboardItem(
810 const base::DictionaryValue& data) {
811 std::string mime_type;
812 std::string item;
813 if (!data.GetString("mimeType", &mime_type) ||
814 !data.GetString("item", &item)) {
815 LOG(ERROR) << "Invalid sendClipboardItem data.";
816 return;
818 if (!IsConnected()) {
819 return;
821 protocol::ClipboardEvent event;
822 event.set_mime_type(mime_type);
823 event.set_data(item);
824 client_->clipboard_forwarder()->InjectClipboardEvent(event);
827 void ChromotingInstance::HandleNotifyClientResolution(
828 const base::DictionaryValue& data) {
829 int width = 0;
830 int height = 0;
831 int x_dpi = kDefaultDPI;
832 int y_dpi = kDefaultDPI;
833 if (!data.GetInteger("width", &width) ||
834 !data.GetInteger("height", &height) ||
835 !data.GetInteger("x_dpi", &x_dpi) ||
836 !data.GetInteger("y_dpi", &y_dpi) ||
837 width <= 0 || height <= 0 ||
838 x_dpi <= 0 || y_dpi <= 0) {
839 LOG(ERROR) << "Invalid notifyClientResolution.";
840 return;
843 if (!IsConnected()) {
844 return;
847 protocol::ClientResolution client_resolution;
848 client_resolution.set_width(width);
849 client_resolution.set_height(height);
850 client_resolution.set_x_dpi(x_dpi);
851 client_resolution.set_y_dpi(y_dpi);
853 // Include the legacy width & height in DIPs for use by older hosts.
854 client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
855 client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
857 client_->host_stub()->NotifyClientResolution(client_resolution);
860 void ChromotingInstance::HandlePauseVideo(const base::DictionaryValue& data) {
861 if (!data.HasKey("pause")) {
862 LOG(ERROR) << "Invalid pauseVideo.";
863 return;
865 HandleVideoControl(data);
868 void ChromotingInstance::HandleVideoControl(const base::DictionaryValue& data) {
869 protocol::VideoControl video_control;
870 bool pause_video = false;
871 if (data.GetBoolean("pause", &pause_video)) {
872 video_control.set_enable(!pause_video);
874 bool lossless_encode = false;
875 if (data.GetBoolean("losslessEncode", &lossless_encode)) {
876 video_control.set_lossless_encode(lossless_encode);
878 bool lossless_color = false;
879 if (data.GetBoolean("losslessColor", &lossless_color)) {
880 video_control.set_lossless_color(lossless_color);
882 if (!IsConnected()) {
883 return;
885 client_->host_stub()->ControlVideo(video_control);
888 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
889 bool pause = false;
890 if (!data.GetBoolean("pause", &pause)) {
891 LOG(ERROR) << "Invalid pauseAudio.";
892 return;
894 if (!IsConnected()) {
895 return;
897 protocol::AudioControl audio_control;
898 audio_control.set_enable(!pause);
899 client_->host_stub()->ControlAudio(audio_control);
901 void ChromotingInstance::HandleOnPinFetched(const base::DictionaryValue& data) {
902 std::string pin;
903 if (!data.GetString("pin", &pin)) {
904 LOG(ERROR) << "Invalid onPinFetched.";
905 return;
907 if (!secret_fetched_callback_.is_null()) {
908 secret_fetched_callback_.Run(pin);
909 secret_fetched_callback_.Reset();
910 } else {
911 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
915 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
916 const base::DictionaryValue& data) {
917 std::string token;
918 std::string shared_secret;
919 if (!data.GetString("token", &token) ||
920 !data.GetString("sharedSecret", &shared_secret)) {
921 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
922 return;
924 if (token_fetcher_proxy_.get()) {
925 token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
926 token_fetcher_proxy_.reset();
927 } else {
928 LOG(WARNING) << "Ignored OnThirdPartyTokenFetched without a pending fetch.";
932 void ChromotingInstance::HandleRequestPairing(
933 const base::DictionaryValue& data) {
934 std::string client_name;
935 if (!data.GetString("clientName", &client_name)) {
936 LOG(ERROR) << "Invalid requestPairing";
937 return;
939 if (!IsConnected()) {
940 return;
942 protocol::PairingRequest pairing_request;
943 pairing_request.set_client_name(client_name);
944 client_->host_stub()->RequestPairing(pairing_request);
947 void ChromotingInstance::HandleExtensionMessage(
948 const base::DictionaryValue& data) {
949 std::string type;
950 std::string message_data;
951 if (!data.GetString("type", &type) ||
952 !data.GetString("data", &message_data)) {
953 LOG(ERROR) << "Invalid extensionMessage.";
954 return;
956 if (!IsConnected()) {
957 return;
959 protocol::ExtensionMessage message;
960 message.set_type(type);
961 message.set_data(message_data);
962 client_->host_stub()->DeliverClientMessage(message);
965 void ChromotingInstance::HandleAllowMouseLockMessage() {
966 // Create the mouse lock handler and route cursor shape messages through it.
967 mouse_locker_.reset(new PepperMouseLocker(
968 this, base::Bind(&PepperInputHandler::set_send_mouse_move_deltas,
969 base::Unretained(&input_handler_)),
970 &cursor_setter_));
971 empty_cursor_filter_.set_cursor_stub(mouse_locker_.get());
974 void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
975 input_handler_.set_send_mouse_input_when_unfocused(true);
978 void ChromotingInstance::HandleDelegateLargeCursors() {
979 cursor_setter_.set_delegate_stub(this);
982 void ChromotingInstance::HandleEnableDebugRegion(
983 const base::DictionaryValue& data) {
984 bool enable = false;
985 if (!data.GetBoolean("enable", &enable)) {
986 LOG(ERROR) << "Invalid enableDebugRegion.";
987 return;
990 video_renderer_->EnableDebugDirtyRegion(enable);
993 void ChromotingInstance::HandleEnableTouchEvents() {
994 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH);
997 void ChromotingInstance::Disconnect() {
998 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1000 VLOG(0) << "Disconnecting from host.";
1002 // Disconnect the input pipeline and teardown the connection.
1003 mouse_input_filter_.set_input_stub(nullptr);
1004 client_.reset();
1005 video_renderer_.reset();
1008 void ChromotingInstance::PostChromotingMessage(const std::string& method,
1009 const pp::VarDictionary& data) {
1010 pp::VarDictionary message;
1011 message.Set(pp::Var("method"), pp::Var(method));
1012 message.Set(pp::Var("data"), data);
1013 PostMessage(message);
1016 void ChromotingInstance::PostLegacyJsonMessage(
1017 const std::string& method,
1018 scoped_ptr<base::DictionaryValue> data) {
1019 scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
1020 message->SetString("method", method);
1021 message->Set("data", data.release());
1023 std::string message_json;
1024 base::JSONWriter::Write(message.get(), &message_json);
1025 PostMessage(pp::Var(message_json));
1028 void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
1029 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1030 data->SetInteger("usbKeycode", usb_keycode);
1031 data->SetBoolean("pressed", pressed);
1032 PostLegacyJsonMessage("trappedKeyEvent", data.Pass());
1035 void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
1036 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1037 data->SetString("iq", iq);
1038 PostLegacyJsonMessage("sendOutgoingIq", data.Pass());
1041 void ChromotingInstance::SendPerfStats() {
1042 if (!video_renderer_.get()) {
1043 return;
1046 plugin_task_runner_->PostDelayedTask(
1047 FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats,
1048 weak_factory_.GetWeakPtr()),
1049 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
1051 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1052 ChromotingStats* stats = video_renderer_->GetStats();
1053 data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
1054 data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
1055 data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
1056 data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
1057 data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
1058 data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
1059 data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
1060 PostLegacyJsonMessage("onPerfStats", data.Pass());
1063 // static
1064 void ChromotingInstance::RegisterLogMessageHandler() {
1065 base::AutoLock lock(g_logging_lock.Get());
1067 VLOG(1) << "Registering global log handler";
1069 // Record previous handler so we can call it in a chain.
1070 g_logging_old_handler = logging::GetLogMessageHandler();
1072 // Set up log message handler.
1073 // This is not thread-safe so we need it within our lock.
1074 logging::SetLogMessageHandler(&LogToUI);
1077 void ChromotingInstance::RegisterLoggingInstance() {
1078 base::AutoLock lock(g_logging_lock.Get());
1080 // Register this instance as the one that will handle all logging calls
1081 // and display them to the user.
1082 // If multiple plugins are run, then the last one registered will handle all
1083 // logging for all instances.
1084 g_logging_instance.Get() = weak_factory_.GetWeakPtr();
1085 g_logging_task_runner.Get() = plugin_task_runner_;
1086 g_has_logging_instance = true;
1089 void ChromotingInstance::UnregisterLoggingInstance() {
1090 base::AutoLock lock(g_logging_lock.Get());
1092 // Don't unregister unless we're the currently registered instance.
1093 if (this != g_logging_instance.Get().get())
1094 return;
1096 // Unregister this instance for logging.
1097 g_has_logging_instance = false;
1098 g_logging_instance.Get().reset();
1099 g_logging_task_runner.Get() = nullptr;
1101 VLOG(1) << "Unregistering global log handler";
1104 // static
1105 bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
1106 size_t message_start,
1107 const std::string& str) {
1108 // Note that we're reading |g_has_logging_instance| outside of a lock.
1109 // This lockless read is done so that we don't needlessly slow down global
1110 // logging with a lock for each log message.
1112 // This lockless read is safe because:
1114 // Misreading a false value (when it should be true) means that we'll simply
1115 // skip processing a few log messages.
1117 // Misreading a true value (when it should be false) means that we'll take
1118 // the lock and check |g_logging_instance| unnecessarily. This is not
1119 // problematic because we always set |g_logging_instance| inside a lock.
1120 if (g_has_logging_instance) {
1121 scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
1122 base::WeakPtr<ChromotingInstance> logging_instance;
1125 base::AutoLock lock(g_logging_lock.Get());
1126 // If we're on the logging thread and |g_logging_to_plugin| is set then
1127 // this LOG message came from handling a previous LOG message and we
1128 // should skip it to avoid an infinite loop of LOG messages.
1129 if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
1130 !g_logging_to_plugin) {
1131 logging_task_runner = g_logging_task_runner.Get();
1132 logging_instance = g_logging_instance.Get();
1136 if (logging_task_runner.get()) {
1137 std::string message = remoting::GetTimestampString();
1138 message += (str.c_str() + message_start);
1140 logging_task_runner->PostTask(
1141 FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
1142 logging_instance, message));
1146 if (g_logging_old_handler)
1147 return (g_logging_old_handler)(severity, file, line, message_start, str);
1148 return false;
1151 void ChromotingInstance::ProcessLogToUI(const std::string& message) {
1152 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1154 // This flag (which is set only here) is used to prevent LogToUI from posting
1155 // new tasks while we're in the middle of servicing a LOG call. This can
1156 // happen if the call to LogDebugInfo tries to LOG anything.
1157 // Since it is read on the plugin thread, we don't need to lock to set it.
1158 g_logging_to_plugin = true;
1159 scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
1160 data->SetString("message", message);
1161 PostLegacyJsonMessage("logDebugMessage", data.Pass());
1162 g_logging_to_plugin = false;
1165 bool ChromotingInstance::IsCallerAppOrExtension() {
1166 const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
1167 if (!url_util)
1168 return false;
1170 PP_URLComponents_Dev url_components;
1171 pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
1172 if (!url_var.is_string())
1173 return false;
1175 std::string url = url_var.AsString();
1176 std::string url_scheme = url.substr(url_components.scheme.begin,
1177 url_components.scheme.len);
1178 return url_scheme == kChromeExtensionUrlScheme;
1181 bool ChromotingInstance::IsConnected() {
1182 return client_ &&
1183 (client_->connection_state() == protocol::ConnectionToHost::CONNECTED);
1186 void ChromotingInstance::LogToWebapp(const std::string& message) {
1187 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
1189 LOG(ERROR) << message;
1191 #if !defined(OS_NACL)
1192 // Log messages are forwarded to the webapp only in PNaCl version of the
1193 // plugin, so ProcessLogToUI() needs to be called explicitly in the non-PNaCl
1194 // version.
1195 ProcessLogToUI(message);
1196 #endif // !defined(OS_NACL)
1199 } // namespace remoting