ExtensionInstallDialogView: fix scrolling behavior on Views (Win,Linux)
[chromium-blink-merge.git] / remoting / client / plugin / chromoting_instance.cc
blobc32d31ded27bcd4d333f047d9af82c1536d5442a
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/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"
56 #include "url/gurl.h"
58 // Windows defines 'PostMessage', so we have to undef it.
59 #if defined(PostMessage)
60 #undef PostMessage
61 #endif
63 namespace remoting {
65 namespace {
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.
87 switch (state) {
88 case protocol::ConnectionToHost::INITIALIZING:
89 return "INITIALIZING";
90 case protocol::ConnectionToHost::CONNECTING:
91 return "CONNECTING";
92 case protocol::ConnectionToHost::AUTHENTICATED:
93 return "AUTHENTICATED";
94 case protocol::ConnectionToHost::CONNECTED:
95 return "CONNECTED";
96 case protocol::ConnectionToHost::CLOSED:
97 return "CLOSED";
98 case protocol::ConnectionToHost::FAILED:
99 return "FAILED";
101 NOTREACHED();
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.
112 switch (error) {
113 case protocol::OK:
114 return "NONE";
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.";
153 return false;
156 return true;
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;
172 } // namespace
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),
185 initialized_(false),
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) {
197 #if defined(OS_NACL)
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_));
202 thread_wrapper_ =
203 jingle_glue::JingleThreadWrapper::WrapTaskRunner(plugin_task_runner_);
204 media::InitializeCPUSpecificYUVConversions();
206 // Register a global log handler.
207 ChromotingInstance::RegisterLogMessageHandler();
208 #else
209 jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
210 #endif
212 #if defined(OS_NACL)
213 nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
214 mount("", "/etc", "memfs", 0, "");
215 mount("", "/usr", "memfs", 0, "");
216 #endif
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));
233 #else
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.
255 Disconnect();
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.
267 context_.Stop();
270 bool ChromotingInstance::Init(uint32_t argc,
271 const char* argn[],
272 const char* argv[]) {
273 CHECK(!initialized_);
274 initialized_ = true;
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";
284 return false;
286 #endif
288 // Start all the threads.
289 context_.Start();
291 return true;
294 void ChromotingInstance::HandleMessage(const pp::Var& message) {
295 if (!message.is_string()) {
296 LOG(ERROR) << "Received a message that is not a string.";
297 return;
300 scoped_ptr<base::Value> json(base::JSONReader::DeprecatedRead(
301 message.AsString(), base::JSON_ALLOW_TRAILING_COMMAS));
302 base::DictionaryValue* message_dict = nullptr;
303 std::string method;
304 base::DictionaryValue* data = nullptr;
305 if (!json.get() ||
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();
310 return;
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());
363 if (!IsConnected())
364 return;
366 input_handler_.DidChangeFocus(has_focus);
367 if (mouse_locker_)
368 mouse_locker_->DidChangeFocus(has_focus);
371 void ChromotingInstance::DidChangeView(const pp::View& view) {
372 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
374 plugin_view_ = view;
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);
380 if (video_renderer_)
381 video_renderer_->OnViewChanged(view);
384 bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
385 DCHECK(plugin_task_runner_->BelongsToCurrentThread());
387 if (!IsConnected())
388 return false;
390 return input_handler_.HandleInputEvent(event);
393 void ChromotingInstance::OnVideoDecodeError() {
394 Disconnect();
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());
417 if (dpi.x())
418 data->SetInteger("x_dpi", dpi.x());
419 if (dpi.y())
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_))
426 return;
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();
450 i.Advance()) {
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.
550 // crbug.com/138108
551 return this;
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());
571 return;
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.";
607 return;
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());
619 } else {
620 std::string shared_secret;
621 if (!data.GetString("sharedSecret", &shared_secret)) {
622 LOG(ERROR) << "sharedSecret not specified in connect().";
623 return;
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.";
634 return;
638 VLOG(0) << "Connecting to " << host_jid
639 << ". Local jid: " << local_jid << ".";
641 #if defined(OS_NACL)
642 std::string key_filter;
643 if (!data.GetString("keyFilter", &key_filter)) {
644 NOTREACHED();
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_));
652 } else {
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_));
660 #else
661 normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
662 #endif
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()),
721 host_public_key));
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());
740 Disconnect();
743 void ChromotingInstance::HandleOnIncomingIq(const base::DictionaryValue& data) {
744 std::string iq;
745 if (!data.GetString("iq", &iq)) {
746 LOG(ERROR) << "Invalid incomingIq() data.";
747 return;
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) {
758 if (IsConnected())
759 input_tracker_.ReleaseAll();
762 void ChromotingInstance::HandleInjectKeyEvent(
763 const base::DictionaryValue& data) {
764 int usb_keycode = 0;
765 bool is_pressed = false;
766 if (!data.GetInteger("usbKeycode", &usb_keycode) ||
767 !data.GetBoolean("pressed", &is_pressed)) {
768 LOG(ERROR) << "Invalid injectKeyEvent.";
769 return;
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.
777 if (IsConnected())
778 input_tracker_.InjectKeyEvent(event);
781 void ChromotingInstance::HandleRemapKey(const base::DictionaryValue& data) {
782 int from_keycode = 0;
783 int to_keycode = 0;
784 if (!data.GetInteger("fromKeycode", &from_keycode) ||
785 !data.GetInteger("toKeycode", &to_keycode)) {
786 LOG(ERROR) << "Invalid remapKey.";
787 return;
790 key_mapper_.RemapKey(from_keycode, to_keycode);
793 void ChromotingInstance::HandleTrapKey(const base::DictionaryValue& data) {
794 int keycode = 0;
795 bool trap = false;
796 if (!data.GetInteger("keycode", &keycode) ||
797 !data.GetBoolean("trap", &trap)) {
798 LOG(ERROR) << "Invalid trapKey.";
799 return;
802 key_mapper_.TrapKey(keycode, trap);
805 void ChromotingInstance::HandleSendClipboardItem(
806 const base::DictionaryValue& data) {
807 std::string mime_type;
808 std::string item;
809 if (!data.GetString("mimeType", &mime_type) ||
810 !data.GetString("item", &item)) {
811 LOG(ERROR) << "Invalid sendClipboardItem data.";
812 return;
814 if (!IsConnected()) {
815 return;
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) {
825 int width = 0;
826 int height = 0;
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.";
836 return;
839 if (!IsConnected()) {
840 return;
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.";
859 return;
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()) {
879 return;
881 client_->host_stub()->ControlVideo(video_control);
884 void ChromotingInstance::HandlePauseAudio(const base::DictionaryValue& data) {
885 bool pause = false;
886 if (!data.GetBoolean("pause", &pause)) {
887 LOG(ERROR) << "Invalid pauseAudio.";
888 return;
890 if (!IsConnected()) {
891 return;
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) {
898 std::string pin;
899 if (!data.GetString("pin", &pin)) {
900 LOG(ERROR) << "Invalid onPinFetched.";
901 return;
903 if (!secret_fetched_callback_.is_null()) {
904 base::ResetAndReturn(&secret_fetched_callback_).Run(pin);
905 } else {
906 LOG(WARNING) << "Ignored OnPinFetched received without a pending fetch.";
910 void ChromotingInstance::HandleOnThirdPartyTokenFetched(
911 const base::DictionaryValue& data) {
912 std::string token;
913 std::string shared_secret;
914 if (!data.GetString("token", &token) ||
915 !data.GetString("sharedSecret", &shared_secret)) {
916 LOG(ERROR) << "Invalid onThirdPartyTokenFetched data.";
917 return;
919 if (token_fetcher_proxy_.get()) {
920 token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
921 token_fetcher_proxy_.reset();
922 } else {
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";
932 return;
934 if (!IsConnected()) {
935 return;
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) {
944 std::string type;
945 std::string message_data;
946 if (!data.GetString("type", &type) ||
947 !data.GetString("data", &message_data)) {
948 LOG(ERROR) << "Invalid extensionMessage.";
949 return;
951 if (!IsConnected()) {
952 return;
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_)),
965 &cursor_setter_));
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) {
979 bool enable = false;
980 if (!data.GetBoolean("enable", &enable)) {
981 LOG(ERROR) << "Invalid enableDebugRegion.";
982 return;
985 video_renderer_->EnableDebugDirtyRegion(enable);
988 void ChromotingInstance::HandleEnableTouchEvents(
989 const base::DictionaryValue& data) {
990 bool enable = false;
991 if (!data.GetBoolean("enable", &enable)) {
992 LOG(ERROR) << "Invalid handleTouchEvents.";
993 return;
996 if (enable) {
997 RequestInputEvents(PP_INPUTEVENT_CLASS_TOUCH);
998 } else {
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);
1010 client_.reset();
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()) {
1049 return;
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());
1069 // static
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())
1100 return;
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";
1110 // static
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);
1154 return false;
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();
1173 if (!url_util)
1174 return false;
1176 PP_URLComponents_Dev url_components;
1177 pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
1178 if (!url_var.is_string())
1179 return false;
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() {
1188 return client_ &&
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
1200 // version.
1201 ProcessLogToUI(message);
1202 #endif // !defined(OS_NACL)
1205 } // namespace remoting