chromoting_instance.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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.
4
5#include "remoting/client/plugin/chromoting_instance.h"
6
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/callback.h"
12#include "base/json/json_reader.h"
13#include "base/json/json_writer.h"
14#include "base/lazy_instance.h"
15#include "base/logging.h"
16#include "base/stringprintf.h"
17#include "base/strings/string_split.h"
18#include "base/synchronization/lock.h"
19#include "base/synchronization/waitable_event.h"
20#include "base/threading/thread.h"
21#include "base/values.h"
22#include "jingle/glue/thread_wrapper.h"
23#include "media/base/media.h"
24#include "net/socket/ssl_server_socket.h"
25#include "ppapi/cpp/completion_callback.h"
26#include "ppapi/cpp/dev/url_util_dev.h"
27#include "ppapi/cpp/input_event.h"
28#include "ppapi/cpp/mouse_cursor.h"
29#include "ppapi/cpp/rect.h"
30#include "remoting/base/constants.h"
31#include "remoting/base/util.h"
32#include "remoting/client/chromoting_client.h"
33#include "remoting/client/client_config.h"
34#include "remoting/client/frame_consumer_proxy.h"
35#include "remoting/client/plugin/pepper_audio_player.h"
36#include "remoting/client/plugin/pepper_input_handler.h"
37#include "remoting/client/plugin/pepper_port_allocator.h"
38#include "remoting/client/plugin/pepper_view.h"
39#include "remoting/client/plugin/pepper_xmpp_proxy.h"
40#include "remoting/client/rectangle_update_decoder.h"
41#include "remoting/protocol/connection_to_host.h"
42#include "remoting/protocol/host_stub.h"
43#include "remoting/protocol/libjingle_transport_factory.h"
44
45// Windows defines 'PostMessage', so we have to undef it.
46#if defined(PostMessage)
47#undef PostMessage
48#endif
49
50namespace remoting {
51
52namespace {
53
54// 32-bit BGRA is 4 bytes per pixel.
55const int kBytesPerPixel = 4;
56
57// Default DPI to assume for old clients that use notifyClientDimensions.
58const int kDefaultDPI = 96;
59
60// Interval at which to sample performance statistics.
61const int kPerfStatsIntervalMs = 1000;
62
63// URL scheme used by Chrome apps and extensions.
64const char kChromeExtensionUrlScheme[] = "chrome-extension";
65
66std::string ConnectionStateToString(protocol::ConnectionToHost::State state) {
67  // Values returned by this function must match the
68  // remoting.ClientSession.State enum in JS code.
69  switch (state) {
70    case protocol::ConnectionToHost::INITIALIZING:
71      return "INITIALIZING";
72    case protocol::ConnectionToHost::CONNECTING:
73      return "CONNECTING";
74    case protocol::ConnectionToHost::CONNECTED:
75      return "CONNECTED";
76    case protocol::ConnectionToHost::CLOSED:
77      return "CLOSED";
78    case protocol::ConnectionToHost::FAILED:
79      return "FAILED";
80  }
81  NOTREACHED();
82  return "";
83}
84
85// TODO(sergeyu): Ideally we should just pass ErrorCode to the webapp
86// and let it handle it, but it would be hard to fix it now because
87// client plugin and webapp versions may not be in sync. It should be
88// easy to do after we are finished moving the client plugin to NaCl.
89std::string ConnectionErrorToString(protocol::ErrorCode error) {
90  // Values returned by this function must match the
91  // remoting.ClientSession.Error enum in JS code.
92  switch (error) {
93    case protocol::OK:
94      return "NONE";
95
96    case protocol::PEER_IS_OFFLINE:
97      return "HOST_IS_OFFLINE";
98
99    case protocol::SESSION_REJECTED:
100    case protocol::AUTHENTICATION_FAILED:
101      return "SESSION_REJECTED";
102
103    case protocol::INCOMPATIBLE_PROTOCOL:
104      return "INCOMPATIBLE_PROTOCOL";
105
106    case protocol::HOST_OVERLOAD:
107      return "HOST_OVERLOAD";
108
109    case protocol::CHANNEL_CONNECTION_ERROR:
110    case protocol::SIGNALING_ERROR:
111    case protocol::SIGNALING_TIMEOUT:
112    case protocol::UNKNOWN_ERROR:
113      return "NETWORK_FAILURE";
114  }
115  DLOG(FATAL) << "Unknown error code" << error;
116  return  "";
117}
118
119// This flag blocks LOGs to the UI if we're already in the middle of logging
120// to the UI. This prevents a potential infinite loop if we encounter an error
121// while sending the log message to the UI.
122bool g_logging_to_plugin = false;
123bool g_has_logging_instance = false;
124base::LazyInstance<scoped_refptr<base::SingleThreadTaskRunner> >::Leaky
125    g_logging_task_runner = LAZY_INSTANCE_INITIALIZER;
126base::LazyInstance<base::WeakPtr<ChromotingInstance> >::Leaky
127    g_logging_instance = LAZY_INSTANCE_INITIALIZER;
128base::LazyInstance<base::Lock>::Leaky
129    g_logging_lock = LAZY_INSTANCE_INITIALIZER;
130logging::LogMessageHandlerFunction g_logging_old_handler = NULL;
131
132}  // namespace
133
134// String sent in the "hello" message to the plugin to describe features.
135const char ChromotingInstance::kApiFeatures[] =
136    "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey "
137    "notifyClientDimensions notifyClientResolution pauseVideo pauseAudio "
138    "asyncPin";
139
140bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str,
141                                          ClientConfig* config) {
142  std::vector<std::string> auth_methods;
143  base::SplitString(auth_methods_str, ',', &auth_methods);
144  for (std::vector<std::string>::iterator it = auth_methods.begin();
145       it != auth_methods.end(); ++it) {
146    protocol::AuthenticationMethod authentication_method =
147        protocol::AuthenticationMethod::FromString(*it);
148    if (authentication_method.is_valid())
149      config->authentication_methods.push_back(authentication_method);
150  }
151  if (config->authentication_methods.empty()) {
152    LOG(ERROR) << "No valid authentication methods specified.";
153    return false;
154  }
155
156  return true;
157}
158
159ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
160    : pp::Instance(pp_instance),
161      initialized_(false),
162      plugin_task_runner_(
163          new PluginThreadTaskRunner(&plugin_thread_delegate_)),
164      context_(plugin_task_runner_),
165      input_tracker_(&mouse_input_filter_),
166#if defined(OS_MACOSX)
167      // On Mac we need an extra filter to inject missing keyup events.
168      // See remoting/client/plugin/mac_key_event_processor.h for more details.
169      mac_key_event_processor_(&input_tracker_),
170      key_mapper_(&mac_key_event_processor_),
171#else
172      key_mapper_(&input_tracker_),
173#endif
174      input_handler_(&key_mapper_),
175      use_async_pin_dialog_(false),
176      weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
177  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
178  RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
179
180  // Resister this instance to handle debug log messsages.
181  RegisterLoggingInstance();
182
183  // Send hello message.
184  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
185  data->SetInteger("apiVersion", kApiVersion);
186  data->SetString("apiFeatures", kApiFeatures);
187  data->SetInteger("apiMinVersion", kApiMinMessagingVersion);
188  PostChromotingMessage("hello", data.Pass());
189}
190
191ChromotingInstance::~ChromotingInstance() {
192  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
193
194  // Unregister this instance so that debug log messages will no longer be sent
195  // to it. This will stop all logging in all Chromoting instances.
196  UnregisterLoggingInstance();
197
198  // PepperView must be destroyed before the client.
199  view_.reset();
200
201  if (client_.get()) {
202    client_->Stop(base::Bind(&PluginThreadTaskRunner::Quit,
203                  plugin_task_runner_));
204  } else {
205    plugin_task_runner_->Quit();
206  }
207
208  // Ensure that nothing touches the plugin thread delegate after this point.
209  plugin_task_runner_->DetachAndRunShutdownLoop();
210
211  // Stopping the context shuts down all chromoting threads.
212  context_.Stop();
213}
214
215bool ChromotingInstance::Init(uint32_t argc,
216                              const char* argn[],
217                              const char* argv[]) {
218  CHECK(!initialized_);
219  initialized_ = true;
220
221  VLOG(1) << "Started ChromotingInstance::Init";
222
223  // Check to make sure the media library is initialized.
224  // http://crbug.com/91521.
225  if (!media::IsMediaLibraryInitialized()) {
226    LOG(ERROR) << "Media library not initialized.";
227    return false;
228  }
229
230  // Check that the calling content is part of an app or extension.
231  if (!IsCallerAppOrExtension()) {
232    LOG(ERROR) << "Not an app or extension";
233    return false;
234  }
235
236  // Enable support for SSL server sockets, which must be done as early as
237  // possible, preferably before any NSS SSL sockets (client or server) have
238  // been created.
239  // It's possible that the hosting process has already made use of SSL, in
240  // which case, there may be a slight race.
241  net::EnableSSLServerSockets();
242
243  // Start all the threads.
244  context_.Start();
245
246  return true;
247}
248
249void ChromotingInstance::HandleMessage(const pp::Var& message) {
250  if (!message.is_string()) {
251    LOG(ERROR) << "Received a message that is not a string.";
252    return;
253  }
254
255  scoped_ptr<base::Value> json(
256      base::JSONReader::Read(message.AsString(),
257                             base::JSON_ALLOW_TRAILING_COMMAS));
258  base::DictionaryValue* message_dict = NULL;
259  std::string method;
260  base::DictionaryValue* data = NULL;
261  if (!json.get() ||
262      !json->GetAsDictionary(&message_dict) ||
263      !message_dict->GetString("method", &method) ||
264      !message_dict->GetDictionary("data", &data)) {
265    LOG(ERROR) << "Received invalid message:" << message.AsString();
266    return;
267  }
268
269  if (method == "connect") {
270    ClientConfig config;
271    std::string auth_methods;
272    if (!data->GetString("hostJid", &config.host_jid) ||
273        !data->GetString("hostPublicKey", &config.host_public_key) ||
274        !data->GetString("localJid", &config.local_jid) ||
275        !data->GetString("authenticationMethods", &auth_methods) ||
276        !ParseAuthMethods(auth_methods, &config) ||
277        !data->GetString("authenticationTag", &config.authentication_tag)) {
278      LOG(ERROR) << "Invalid connect() data.";
279      return;
280    }
281    if (use_async_pin_dialog_) {
282      config.fetch_secret_callback =
283          base::Bind(&ChromotingInstance::FetchSecretFromDialog,
284                     this->AsWeakPtr());
285    } else {
286      std::string shared_secret;
287      if (!data->GetString("sharedSecret", &shared_secret)) {
288        LOG(ERROR) << "sharedSecret not specified in connect().";
289        return;
290      }
291      config.fetch_secret_callback =
292          base::Bind(&ChromotingInstance::FetchSecretFromString, shared_secret);
293    }
294    Connect(config);
295  } else if (method == "disconnect") {
296    Disconnect();
297  } else if (method == "incomingIq") {
298    std::string iq;
299    if (!data->GetString("iq", &iq)) {
300      LOG(ERROR) << "Invalid onIq() data.";
301      return;
302    }
303    OnIncomingIq(iq);
304  } else if (method == "releaseAllKeys") {
305    ReleaseAllKeys();
306  } else if (method == "injectKeyEvent") {
307    int usb_keycode = 0;
308    bool is_pressed = false;
309    if (!data->GetInteger("usbKeycode", &usb_keycode) ||
310        !data->GetBoolean("pressed", &is_pressed)) {
311      LOG(ERROR) << "Invalid injectKeyEvent.";
312      return;
313    }
314
315    protocol::KeyEvent event;
316    event.set_usb_keycode(usb_keycode);
317    event.set_pressed(is_pressed);
318    InjectKeyEvent(event);
319  } else if (method == "remapKey") {
320    int from_keycode = 0;
321    int to_keycode = 0;
322    if (!data->GetInteger("fromKeycode", &from_keycode) ||
323        !data->GetInteger("toKeycode", &to_keycode)) {
324      LOG(ERROR) << "Invalid remapKey.";
325      return;
326    }
327
328    RemapKey(from_keycode, to_keycode);
329  } else if (method == "trapKey") {
330    int keycode = 0;
331    bool trap = false;
332    if (!data->GetInteger("keycode", &keycode) ||
333        !data->GetBoolean("trap", &trap)) {
334      LOG(ERROR) << "Invalid trapKey.";
335      return;
336    }
337
338    TrapKey(keycode, trap);
339  } else if (method == "sendClipboardItem") {
340    std::string mime_type;
341    std::string item;
342    if (!data->GetString("mimeType", &mime_type) ||
343        !data->GetString("item", &item)) {
344      LOG(ERROR) << "Invalid sendClipboardItem() data.";
345      return;
346    }
347    SendClipboardItem(mime_type, item);
348  } else if (method == "notifyClientDimensions" ||
349             method == "notifyClientResolution") {
350    // notifyClientResolution's width and height are in pixels,
351    // notifyClientDimension's in DIPs, but since for the latter
352    // we assume 96dpi, DIPs and pixels are equivalent.
353    int width = 0;
354    int height = 0;
355    if (!data->GetInteger("width", &width) ||
356        !data->GetInteger("height", &height) ||
357        width <= 0 || height <= 0) {
358      LOG(ERROR) << "Invalid " << method << ".";
359      return;
360    }
361
362    // notifyClientResolution requires that DPI be specified.
363    // For notifyClientDimensions we assume 96dpi.
364    int x_dpi = kDefaultDPI;
365    int y_dpi = kDefaultDPI;
366    if (method == "notifyClientResolution" &&
367        (!data->GetInteger("x_dpi", &x_dpi) ||
368         !data->GetInteger("y_dpi", &y_dpi) ||
369         x_dpi <= 0 || y_dpi <= 0)) {
370      LOG(ERROR) << "Invalid notifyClientResolution.";
371      return;
372    }
373
374    NotifyClientResolution(width, height, x_dpi, y_dpi);
375  } else if (method == "pauseVideo") {
376    bool pause = false;
377    if (!data->GetBoolean("pause", &pause)) {
378      LOG(ERROR) << "Invalid pauseVideo.";
379      return;
380    }
381    PauseVideo(pause);
382  } else if (method == "pauseAudio") {
383    bool pause = false;
384    if (!data->GetBoolean("pause", &pause)) {
385      LOG(ERROR) << "Invalid pauseAudio.";
386      return;
387    }
388    PauseAudio(pause);
389  } else if (method == "useAsyncPinDialog") {
390    use_async_pin_dialog_ = true;
391  } else if (method == "onPinFetched") {
392    std::string pin;
393    if (!data->GetString("pin", &pin)) {
394      LOG(ERROR) << "Invalid onPinFetched.";
395      return;
396    }
397    OnPinFetched(pin);
398  }
399}
400
401void ChromotingInstance::DidChangeView(const pp::View& view) {
402  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
403
404  plugin_view_ = view;
405  if (view_) {
406    view_->SetView(view);
407    mouse_input_filter_.set_input_size(view_->get_view_size_dips());
408  }
409}
410
411bool ChromotingInstance::HandleInputEvent(const pp::InputEvent& event) {
412  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
413
414  if (!IsConnected())
415    return false;
416
417  return input_handler_.HandleInputEvent(event);
418}
419
420void ChromotingInstance::SetDesktopSize(const SkISize& size,
421                                        const SkIPoint& dpi) {
422  mouse_input_filter_.set_output_size(size);
423
424  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
425  data->SetInteger("width", size.width());
426  data->SetInteger("height", size.height());
427  if (dpi.x())
428    data->SetInteger("x_dpi", dpi.x());
429  if (dpi.y())
430    data->SetInteger("y_dpi", dpi.y());
431  PostChromotingMessage("onDesktopSize", data.Pass());
432}
433
434void ChromotingInstance::OnConnectionState(
435    protocol::ConnectionToHost::State state,
436    protocol::ErrorCode error) {
437  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
438  data->SetString("state", ConnectionStateToString(state));
439  data->SetString("error", ConnectionErrorToString(error));
440  PostChromotingMessage("onConnectionStatus", data.Pass());
441}
442
443void ChromotingInstance::OnConnectionReady(bool ready) {
444  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
445  data->SetBoolean("ready", ready);
446  PostChromotingMessage("onConnectionReady", data.Pass());
447}
448
449void ChromotingInstance::FetchSecretFromDialog(
450    const protocol::SecretFetchedCallback& secret_fetched_callback) {
451  // Once the Session object calls this function, it won't continue the
452  // authentication until the callback is called (or connection is canceled).
453  // So, it's impossible to reach this with a callback already registered.
454  DCHECK(secret_fetched_callback_.is_null());
455  secret_fetched_callback_ = secret_fetched_callback;
456  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
457  PostChromotingMessage("fetchPin", data.Pass());
458}
459
460void ChromotingInstance::FetchSecretFromString(
461    const std::string& shared_secret,
462    const protocol::SecretFetchedCallback& secret_fetched_callback) {
463  secret_fetched_callback.Run(shared_secret);
464}
465
466protocol::ClipboardStub* ChromotingInstance::GetClipboardStub() {
467  // TODO(sergeyu): Move clipboard handling to a separate class.
468  // crbug.com/138108
469  return this;
470}
471
472protocol::CursorShapeStub* ChromotingInstance::GetCursorShapeStub() {
473  // TODO(sergeyu): Move cursor shape code to a separate class.
474  // crbug.com/138108
475  return this;
476}
477
478void ChromotingInstance::InjectClipboardEvent(
479    const protocol::ClipboardEvent& event) {
480  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
481  data->SetString("mimeType", event.mime_type());
482  data->SetString("item", event.data());
483  PostChromotingMessage("injectClipboardItem", data.Pass());
484}
485
486void ChromotingInstance::SetCursorShape(
487    const protocol::CursorShapeInfo& cursor_shape) {
488  if (!cursor_shape.has_data() ||
489      !cursor_shape.has_width() ||
490      !cursor_shape.has_height() ||
491      !cursor_shape.has_hotspot_x() ||
492      !cursor_shape.has_hotspot_y()) {
493    return;
494  }
495
496  int width = cursor_shape.width();
497  int height = cursor_shape.height();
498
499  if (width < 0 || height < 0) {
500    return;
501  }
502
503  if (width > 32 || height > 32) {
504    VLOG(2) << "Cursor too large for SetCursor: "
505            << width << "x" << height << " > 32x32";
506    return;
507  }
508
509  uint32 cursor_total_bytes = width * height * kBytesPerPixel;
510  if (cursor_shape.data().size() < cursor_total_bytes) {
511    VLOG(2) << "Expected " << cursor_total_bytes << " bytes for a "
512            << width << "x" << height << " cursor. Only received "
513            << cursor_shape.data().size() << " bytes";
514    return;
515  }
516
517  if (pp::ImageData::GetNativeImageDataFormat() !=
518      PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
519    VLOG(2) << "Unable to set cursor shape - non-native image format";
520    return;
521  }
522
523  int hotspot_x = cursor_shape.hotspot_x();
524  int hotspot_y = cursor_shape.hotspot_y();
525
526  pp::ImageData cursor_image(this, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
527                             pp::Size(width, height), false);
528
529  int bytes_per_row = width * kBytesPerPixel;
530  const uint8* src_row_data = reinterpret_cast<const uint8*>(
531      cursor_shape.data().data());
532  uint8* dst_row_data = reinterpret_cast<uint8*>(cursor_image.data());
533  for (int row = 0; row < height; row++) {
534    memcpy(dst_row_data, src_row_data, bytes_per_row);
535    src_row_data += bytes_per_row;
536    dst_row_data += cursor_image.stride();
537  }
538
539  pp::MouseCursor::SetCursor(this, PP_MOUSECURSOR_TYPE_CUSTOM,
540                             cursor_image,
541                             pp::Point(hotspot_x, hotspot_y));
542}
543
544void ChromotingInstance::OnFirstFrameReceived() {
545  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
546  PostChromotingMessage("onFirstFrameReceived", data.Pass());
547}
548
549void ChromotingInstance::Connect(const ClientConfig& config) {
550  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
551
552  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
553
554  // RectangleUpdateDecoder runs on a separate thread so for now we wrap
555  // PepperView with a ref-counted proxy object.
556  scoped_refptr<FrameConsumerProxy> consumer_proxy =
557      new FrameConsumerProxy(plugin_task_runner_);
558
559  host_connection_.reset(new protocol::ConnectionToHost(true));
560  scoped_ptr<AudioPlayer> audio_player(new PepperAudioPlayer(this));
561  client_.reset(new ChromotingClient(config, &context_,
562                                     host_connection_.get(), this,
563                                     consumer_proxy, audio_player.Pass()));
564
565  view_.reset(new PepperView(this, &context_, client_->GetFrameProducer()));
566  consumer_proxy->Attach(view_->AsWeakPtr());
567  if (!plugin_view_.is_null()) {
568    view_->SetView(plugin_view_);
569  }
570
571  // Connect the input pipeline to the protocol stub & initialize components.
572  mouse_input_filter_.set_input_stub(host_connection_->input_stub());
573  mouse_input_filter_.set_input_size(view_->get_view_size_dips());
574
575  LOG(INFO) << "Connecting to " << config.host_jid
576            << ". Local jid: " << config.local_jid << ".";
577
578  // Setup the XMPP Proxy.
579  xmpp_proxy_ = new PepperXmppProxy(
580      base::Bind(&ChromotingInstance::SendOutgoingIq, AsWeakPtr()),
581      plugin_task_runner_, context_.main_task_runner());
582
583  scoped_ptr<cricket::HttpPortAllocatorBase> port_allocator(
584      PepperPortAllocator::Create(this));
585  scoped_ptr<protocol::TransportFactory> transport_factory(
586      new protocol::LibjingleTransportFactory(port_allocator.Pass(), false));
587
588  // Kick off the connection.
589  client_->Start(xmpp_proxy_, transport_factory.Pass());
590
591  // Start timer that periodically sends perf stats.
592  plugin_task_runner_->PostDelayedTask(
593      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
594      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
595}
596
597void ChromotingInstance::Disconnect() {
598  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
599
600  // PepperView must be destroyed before the client.
601  view_.reset();
602
603  LOG(INFO) << "Disconnecting from host.";
604  if (client_.get()) {
605    // TODO(sergeyu): Should we disconnect asynchronously?
606    base::WaitableEvent done_event(true, false);
607    client_->Stop(base::Bind(&base::WaitableEvent::Signal,
608                             base::Unretained(&done_event)));
609    done_event.Wait();
610    client_.reset();
611  }
612
613  // Disconnect the input pipeline and teardown the connection.
614  mouse_input_filter_.set_input_stub(NULL);
615  host_connection_.reset();
616}
617
618void ChromotingInstance::OnIncomingIq(const std::string& iq) {
619  xmpp_proxy_->OnIq(iq);
620}
621
622void ChromotingInstance::ReleaseAllKeys() {
623  if (IsConnected())
624    input_tracker_.ReleaseAll();
625}
626
627void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent& event) {
628  // Inject after the KeyEventMapper, so the event won't get mapped or trapped.
629  if (IsConnected())
630    input_tracker_.InjectKeyEvent(event);
631}
632
633void ChromotingInstance::RemapKey(uint32 in_usb_keycode,
634                                  uint32 out_usb_keycode) {
635  key_mapper_.RemapKey(in_usb_keycode, out_usb_keycode);
636}
637
638void ChromotingInstance::TrapKey(uint32 usb_keycode, bool trap) {
639  key_mapper_.TrapKey(usb_keycode, trap);
640}
641
642void ChromotingInstance::SendClipboardItem(const std::string& mime_type,
643                                           const std::string& item) {
644  if (!IsConnected()) {
645    return;
646  }
647  protocol::ClipboardEvent event;
648  event.set_mime_type(mime_type);
649  event.set_data(item);
650  host_connection_->clipboard_stub()->InjectClipboardEvent(event);
651}
652
653void ChromotingInstance::NotifyClientResolution(int width,
654                                                int height,
655                                                int x_dpi,
656                                                int y_dpi) {
657  if (!IsConnected()) {
658    return;
659  }
660
661  protocol::ClientResolution client_resolution;
662  client_resolution.set_width(width);
663  client_resolution.set_height(height);
664  client_resolution.set_x_dpi(x_dpi);
665  client_resolution.set_y_dpi(y_dpi);
666
667  // Include the legacy width & height in DIPs for use by older hosts.
668  client_resolution.set_dips_width((width * kDefaultDPI) / x_dpi);
669  client_resolution.set_dips_height((height * kDefaultDPI) / y_dpi);
670
671  host_connection_->host_stub()->NotifyClientResolution(client_resolution);
672}
673
674void ChromotingInstance::PauseVideo(bool pause) {
675  if (!IsConnected()) {
676    return;
677  }
678  protocol::VideoControl video_control;
679  video_control.set_enable(!pause);
680  host_connection_->host_stub()->ControlVideo(video_control);
681}
682
683void ChromotingInstance::PauseAudio(bool pause) {
684  if (!IsConnected()) {
685    return;
686  }
687  protocol::AudioControl audio_control;
688  audio_control.set_enable(!pause);
689  host_connection_->host_stub()->ControlAudio(audio_control);
690}
691
692void ChromotingInstance::OnPinFetched(const std::string& pin) {
693  if (!secret_fetched_callback_.is_null()) {
694    secret_fetched_callback_.Run(pin);
695    secret_fetched_callback_.Reset();
696  } else {
697    VLOG(1) << "Ignored OnPinFetched received without a pending fetch.";
698  }
699}
700
701ChromotingStats* ChromotingInstance::GetStats() {
702  if (!client_.get())
703    return NULL;
704  return client_->GetStats();
705}
706
707void ChromotingInstance::PostChromotingMessage(
708    const std::string& method,
709    scoped_ptr<base::DictionaryValue> data) {
710  scoped_ptr<base::DictionaryValue> message(new base::DictionaryValue());
711  message->SetString("method", method);
712  message->Set("data", data.release());
713
714  std::string message_json;
715  base::JSONWriter::Write(message.get(), &message_json);
716  PostMessage(pp::Var(message_json));
717}
718
719void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) {
720  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
721  data->SetInteger("usbKeycode", usb_keycode);
722  data->SetBoolean("pressed", pressed);
723  PostChromotingMessage("trappedKeyEvent", data.Pass());
724}
725
726void ChromotingInstance::SendOutgoingIq(const std::string& iq) {
727  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
728  data->SetString("iq", iq);
729  PostChromotingMessage("sendOutgoingIq", data.Pass());
730}
731
732void ChromotingInstance::SendPerfStats() {
733  if (!client_.get()) {
734    return;
735  }
736
737  plugin_task_runner_->PostDelayedTask(
738      FROM_HERE, base::Bind(&ChromotingInstance::SendPerfStats, AsWeakPtr()),
739      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
740
741  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
742  ChromotingStats* stats = client_->GetStats();
743  data->SetDouble("videoBandwidth", stats->video_bandwidth()->Rate());
744  data->SetDouble("videoFrameRate", stats->video_frame_rate()->Rate());
745  data->SetDouble("captureLatency", stats->video_capture_ms()->Average());
746  data->SetDouble("encodeLatency", stats->video_encode_ms()->Average());
747  data->SetDouble("decodeLatency", stats->video_decode_ms()->Average());
748  data->SetDouble("renderLatency", stats->video_paint_ms()->Average());
749  data->SetDouble("roundtripLatency", stats->round_trip_ms()->Average());
750  PostChromotingMessage("onPerfStats", data.Pass());
751}
752
753// static
754void ChromotingInstance::RegisterLogMessageHandler() {
755  base::AutoLock lock(g_logging_lock.Get());
756
757  VLOG(1) << "Registering global log handler";
758
759  // Record previous handler so we can call it in a chain.
760  g_logging_old_handler = logging::GetLogMessageHandler();
761
762  // Set up log message handler.
763  // This is not thread-safe so we need it within our lock.
764  logging::SetLogMessageHandler(&LogToUI);
765}
766
767void ChromotingInstance::RegisterLoggingInstance() {
768  base::AutoLock lock(g_logging_lock.Get());
769
770  // Register this instance as the one that will handle all logging calls
771  // and display them to the user.
772  // If multiple plugins are run, then the last one registered will handle all
773  // logging for all instances.
774  g_logging_instance.Get() = weak_factory_.GetWeakPtr();
775  g_logging_task_runner.Get() = plugin_task_runner_;
776  g_has_logging_instance = true;
777}
778
779void ChromotingInstance::UnregisterLoggingInstance() {
780  base::AutoLock lock(g_logging_lock.Get());
781
782  // Don't unregister unless we're the currently registered instance.
783  if (this != g_logging_instance.Get().get())
784    return;
785
786  // Unregister this instance for logging.
787  g_has_logging_instance = false;
788  g_logging_instance.Get().reset();
789  g_logging_task_runner.Get() = NULL;
790
791  VLOG(1) << "Unregistering global log handler";
792}
793
794// static
795bool ChromotingInstance::LogToUI(int severity, const char* file, int line,
796                                 size_t message_start,
797                                 const std::string& str) {
798  // Note that we're reading |g_has_logging_instance| outside of a lock.
799  // This lockless read is done so that we don't needlessly slow down global
800  // logging with a lock for each log message.
801  //
802  // This lockless read is safe because:
803  //
804  // Misreading a false value (when it should be true) means that we'll simply
805  // skip processing a few log messages.
806  //
807  // Misreading a true value (when it should be false) means that we'll take
808  // the lock and check |g_logging_instance| unnecessarily. This is not
809  // problematic because we always set |g_logging_instance| inside a lock.
810  if (g_has_logging_instance) {
811    scoped_refptr<base::SingleThreadTaskRunner> logging_task_runner;
812    base::WeakPtr<ChromotingInstance> logging_instance;
813
814    {
815      base::AutoLock lock(g_logging_lock.Get());
816      // If we're on the logging thread and |g_logging_to_plugin| is set then
817      // this LOG message came from handling a previous LOG message and we
818      // should skip it to avoid an infinite loop of LOG messages.
819      if (!g_logging_task_runner.Get()->BelongsToCurrentThread() ||
820          !g_logging_to_plugin) {
821        logging_task_runner = g_logging_task_runner.Get();
822        logging_instance = g_logging_instance.Get();
823      }
824    }
825
826    if (logging_task_runner.get()) {
827      std::string message = remoting::GetTimestampString();
828      message += (str.c_str() + message_start);
829
830      logging_task_runner->PostTask(
831          FROM_HERE, base::Bind(&ChromotingInstance::ProcessLogToUI,
832                                logging_instance, message));
833    }
834  }
835
836  if (g_logging_old_handler)
837    return (g_logging_old_handler)(severity, file, line, message_start, str);
838  return false;
839}
840
841void ChromotingInstance::ProcessLogToUI(const std::string& message) {
842  DCHECK(plugin_task_runner_->BelongsToCurrentThread());
843
844  // This flag (which is set only here) is used to prevent LogToUI from posting
845  // new tasks while we're in the middle of servicing a LOG call. This can
846  // happen if the call to LogDebugInfo tries to LOG anything.
847  // Since it is read on the plugin thread, we don't need to lock to set it.
848  g_logging_to_plugin = true;
849  scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
850  data->SetString("message", message);
851  PostChromotingMessage("logDebugMessage", data.Pass());
852  g_logging_to_plugin = false;
853}
854
855bool ChromotingInstance::IsCallerAppOrExtension() {
856  const pp::URLUtil_Dev* url_util = pp::URLUtil_Dev::Get();
857  if (!url_util)
858    return false;
859
860  PP_URLComponents_Dev url_components;
861  pp::Var url_var = url_util->GetDocumentURL(this, &url_components);
862  if (!url_var.is_string())
863    return false;
864
865  std::string url = url_var.AsString();
866  std::string url_scheme = url.substr(url_components.scheme.begin,
867                                      url_components.scheme.len);
868  return url_scheme == kChromeExtensionUrlScheme;
869}
870
871bool ChromotingInstance::IsConnected() {
872  return host_connection_.get() &&
873    (host_connection_->state() == protocol::ConnectionToHost::CONNECTED);
874}
875
876}  // namespace remoting
877