summaryrefslogtreecommitdiff
path: root/remoting
diff options
context:
space:
mode:
authorTorne (Richard Coles) <torne@google.com>2014-06-03 10:58:34 +0100
committerTorne (Richard Coles) <torne@google.com>2014-06-03 10:58:34 +0100
commitcedac228d2dd51db4b79ea1e72c7f249408ee061 (patch)
treeaa4ff43d7fe316e95d12721ce5e17653a768a0dd /remoting
parent6a869ecff032b5bed299d661b078b0555034598b (diff)
downloadchromium_org-cedac228d2dd51db4b79ea1e72c7f249408ee061.tar.gz
Merge from Chromium at DEPS revision 273901
This commit was generated by merge_to_master.py. Change-Id: I45745444894df927ffc1045ab8de88b9e52636a3
Diffstat (limited to 'remoting')
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Chromoting.java3
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Desktop.java20
-rw-r--r--remoting/base/capabilities.cc14
-rw-r--r--remoting/base/util.cc22
-rw-r--r--remoting/base/util.h13
-rw-r--r--remoting/base/util_unittest.cc22
-rw-r--r--remoting/client/chromoting_client.cc15
-rw-r--r--remoting/client/jni/chromoting_jni_instance.cc7
-rw-r--r--remoting/client/jni/chromoting_jni_runtime.cc2
-rw-r--r--remoting/client/log_to_server.cc22
-rw-r--r--remoting/client/log_to_server.h2
-rw-r--r--remoting/client/plugin/chromoting_instance.cc88
-rw-r--r--remoting/client/plugin/chromoting_instance.h12
-rw-r--r--remoting/client/plugin/media_source_video_renderer.cc18
-rw-r--r--remoting/client/plugin/normalizing_input_filter.cc20
-rw-r--r--remoting/client/plugin/normalizing_input_filter.h21
-rw-r--r--remoting/client/plugin/normalizing_input_filter_cros.cc192
-rw-r--r--remoting/client/plugin/normalizing_input_filter_cros.h54
-rw-r--r--remoting/client/plugin/normalizing_input_filter_cros_unittest.cc37
-rw-r--r--remoting/client/plugin/normalizing_input_filter_mac.cc64
-rw-r--r--remoting/client/plugin/normalizing_input_filter_mac.h65
-rw-r--r--remoting/client/plugin/normalizing_input_filter_mac_unittest.cc27
-rw-r--r--remoting/client/plugin/pepper_input_handler.cc27
-rw-r--r--remoting/client/plugin/pepper_input_handler.h16
-rw-r--r--remoting/client/plugin/pepper_module.cc32
-rw-r--r--remoting/client/server_log_entry.h93
-rw-r--r--remoting/client/server_log_entry_client.cc (renamed from remoting/client/server_log_entry.cc)226
-rw-r--r--remoting/client/server_log_entry_client.h41
-rw-r--r--remoting/client/server_log_entry_client_unittest.cc125
-rw-r--r--remoting/codec/video_decoder_vpx.cc195
-rw-r--r--remoting/codec/video_encoder.h4
-rw-r--r--remoting/codec/video_encoder_vpx.cc236
-rw-r--r--remoting/codec/video_encoder_vpx.h25
-rw-r--r--remoting/codec/video_encoder_vpx_unittest.cc97
-rw-r--r--remoting/host/DEPS1
-rw-r--r--remoting/host/branding.cc4
-rw-r--r--remoting/host/chromoting_host.cc40
-rw-r--r--remoting/host/chromoting_host.h12
-rw-r--r--remoting/host/client_session.cc42
-rw-r--r--remoting/host/client_session.h22
-rw-r--r--remoting/host/client_session_unittest.cc201
-rw-r--r--remoting/host/curtain_mode_win.cc2
-rw-r--r--remoting/host/daemon_process.cc3
-rw-r--r--remoting/host/daemon_process_win.cc14
-rw-r--r--remoting/host/desktop_process_main.cc3
-rw-r--r--remoting/host/desktop_resizer_linux.cc4
-rw-r--r--remoting/host/desktop_session_proxy.cc3
-rw-r--r--remoting/host/desktop_shape_tracker_win.cc2
-rw-r--r--remoting/host/heartbeat_sender.cc7
-rw-r--r--remoting/host/host_event_logger_win.cc5
-rw-r--r--remoting/host/host_extension.h40
-rw-r--r--remoting/host/host_extension_session.h33
-rw-r--r--remoting/host/host_main.cc26
-rw-r--r--remoting/host/host_mock_objects.h1
-rw-r--r--remoting/host/host_status_sender.cc7
-rw-r--r--remoting/host/input_injector_win.cc4
-rw-r--r--remoting/host/ipc_util_win.cc10
-rw-r--r--remoting/host/it2me/it2me_host.cc7
-rw-r--r--remoting/host/it2me/it2me_host.h9
-rw-r--r--remoting/host/it2me/it2me_native_messaging_host_main.cc3
-rwxr-xr-xremoting/host/linux/linux_me2me_host.py44
-rw-r--r--remoting/host/local_input_monitor_win.cc8
-rw-r--r--remoting/host/log_to_server.cc9
-rw-r--r--remoting/host/log_to_server.h2
-rw-r--r--remoting/host/oauth_token_getter.cc37
-rw-r--r--remoting/host/oauth_token_getter.h7
-rw-r--r--remoting/host/plugin/host_plugin.cc8
-rw-r--r--remoting/host/remoting_me2me_host.cc13
-rw-r--r--remoting/host/sas_injector_win.cc22
-rw-r--r--remoting/host/server_log_entry.cc171
-rw-r--r--remoting/host/server_log_entry.h74
-rw-r--r--remoting/host/server_log_entry_host.cc107
-rw-r--r--remoting/host/server_log_entry_host.h37
-rw-r--r--remoting/host/server_log_entry_host_unittest.cc (renamed from remoting/host/server_log_entry_unittest.cc)80
-rw-r--r--remoting/host/service_urls.cc2
-rw-r--r--remoting/host/setup/daemon_controller_delegate_linux.cc15
-rw-r--r--remoting/host/setup/daemon_controller_delegate_win.cc12
-rw-r--r--remoting/host/setup/me2me_native_messaging_host.cc35
-rw-r--r--remoting/host/setup/me2me_native_messaging_host_main.cc11
-rw-r--r--remoting/host/setup/start_host.cc5
-rw-r--r--remoting/host/usage_stats_consent_mac.cc2
-rw-r--r--remoting/host/usage_stats_consent_win.cc3
-rw-r--r--remoting/host/video_scheduler.cc77
-rw-r--r--remoting/host/video_scheduler.h19
-rw-r--r--remoting/host/win/com_security.cc4
-rw-r--r--remoting/host/win/elevated_controller.cc46
-rw-r--r--remoting/host/win/host_service.cc24
-rw-r--r--remoting/host/win/launch_process_with_token.cc49
-rw-r--r--remoting/host/win/rdp_client_window.cc2
-rw-r--r--remoting/host/win/unprivileged_process_delegate.cc28
-rw-r--r--remoting/host/win/worker_process_launcher.cc3
-rw-r--r--remoting/host/win/wts_session_process_delegate.cc22
-rw-r--r--remoting/host/win/wts_terminal_monitor.cc2
-rw-r--r--remoting/ios/Chromoting/Base.lproj/Main.storyboard426
-rw-r--r--remoting/ios/Chromoting/Chromoting-Info.plist48
-rw-r--r--remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/.xccurrentversion8
-rw-r--r--remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/ChromotingModel.xcdatamodel/contents13
-rw-r--r--remoting/ios/Chromoting/GTMOAuth2ViewTouch.xib494
-rw-r--r--remoting/ios/Chromoting/Images.xcassets/AppIcon.appiconset/Contents.json91
-rw-r--r--remoting/ios/Chromoting/Images.xcassets/LaunchImage.launchimage/Contents.json36
-rw-r--r--remoting/ios/Chromoting/en.lproj/InfoPlist.strings3
-rw-r--r--remoting/ios/Chromoting/main.mm44
-rw-r--r--remoting/ios/Chromoting_unittests/Chromoting_unittests-Info.plist48
-rw-r--r--remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/.xccurrentversion8
-rw-r--r--remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/Chromoting_unittests.xcdatamodel/contents4
-rw-r--r--remoting/ios/Chromoting_unittests/Images.xcassets/AppIcon.appiconset/Contents.json53
-rw-r--r--remoting/ios/Chromoting_unittests/Images.xcassets/LaunchImage.launchimage/Contents.json51
-rw-r--r--remoting/ios/Chromoting_unittests/en.lproj/InfoPlist.strings2
-rw-r--r--remoting/ios/Chromoting_unittests/main.mm24
-rw-r--r--remoting/ios/Chromoting_unittests/main_no_arc.cc20
-rw-r--r--remoting/ios/Chromoting_unittests/main_no_arc.h11
-rw-r--r--remoting/ios/DEPS3
-rw-r--r--remoting/ios/app_delegate.h17
-rw-r--r--remoting/ios/app_delegate.mm18
-rw-r--r--remoting/ios/authorize.h30
-rw-r--r--remoting/ios/authorize.mm123
-rw-r--r--remoting/ios/bridge/DEPS8
-rw-r--r--remoting/ios/bridge/client_instance.cc397
-rw-r--r--remoting/ios/bridge/client_instance.h164
-rw-r--r--remoting/ios/bridge/client_instance_unittest.mm319
-rw-r--r--remoting/ios/bridge/client_proxy.h62
-rw-r--r--remoting/ios/bridge/client_proxy.mm150
-rw-r--r--remoting/ios/bridge/client_proxy_delegate.h43
-rw-r--r--remoting/ios/bridge/client_proxy_delegate_wrapper.h33
-rw-r--r--remoting/ios/bridge/client_proxy_delegate_wrapper.mm31
-rw-r--r--remoting/ios/bridge/client_proxy_unittest.mm366
-rw-r--r--remoting/ios/bridge/frame_consumer_bridge.cc88
-rw-r--r--remoting/ios/bridge/frame_consumer_bridge.h65
-rw-r--r--remoting/ios/bridge/frame_consumer_bridge_unittest.cc138
-rw-r--r--remoting/ios/bridge/host_proxy.h67
-rw-r--r--remoting/ios/bridge/host_proxy.mm119
-rw-r--r--remoting/ios/bridge/host_proxy_unittest.mm51
-rw-r--r--remoting/ios/data_store.h31
-rw-r--r--remoting/ios/data_store.mm176
-rw-r--r--remoting/ios/data_store_unittest.mm119
-rw-r--r--remoting/ios/host.h28
-rw-r--r--remoting/ios/host.mm59
-rw-r--r--remoting/ios/host_cell.h20
-rw-r--r--remoting/ios/host_cell.mm20
-rw-r--r--remoting/ios/host_preferences.h32
-rw-r--r--remoting/ios/host_refresh.h37
-rw-r--r--remoting/ios/host_refresh.mm132
-rw-r--r--remoting/ios/host_refresh_test_helper.h102
-rw-r--r--remoting/ios/host_refresh_unittest.mm170
-rw-r--r--remoting/ios/key_input.h35
-rw-r--r--remoting/ios/key_input.mm111
-rw-r--r--remoting/ios/key_input_unittest.mm124
-rw-r--r--remoting/ios/key_map_us.h288
-rw-r--r--remoting/ios/ui/cursor_texture.h58
-rw-r--r--remoting/ios/ui/cursor_texture.mm181
-rw-r--r--remoting/ios/ui/desktop_texture.h38
-rw-r--r--remoting/ios/ui/desktop_texture.mm83
-rw-r--r--remoting/ios/ui/help_view_controller.h17
-rw-r--r--remoting/ios/ui/help_view_controller.mm21
-rw-r--r--remoting/ios/ui/host_list_view_controller.h39
-rw-r--r--remoting/ios/ui/host_list_view_controller.mm229
-rw-r--r--remoting/ios/ui/host_list_view_controller_unittest.mm90
-rw-r--r--remoting/ios/ui/host_view_controller.h115
-rw-r--r--remoting/ios/ui/host_view_controller.mm676
-rw-r--r--remoting/ios/ui/pin_entry_view_controller.h49
-rw-r--r--remoting/ios/ui/pin_entry_view_controller.mm71
-rw-r--r--remoting/ios/ui/pin_entry_view_controller_ipad.xib103
-rw-r--r--remoting/ios/ui/pin_entry_view_controller_iphone.xib113
-rw-r--r--remoting/ios/ui/scene_view.h171
-rw-r--r--remoting/ios/ui/scene_view.mm642
-rw-r--r--remoting/ios/ui/scene_view_unittest.mm1219
-rw-r--r--remoting/ios/utility.h64
-rw-r--r--remoting/ios/utility.mm150
-rw-r--r--remoting/jingle_glue/server_log_entry.cc88
-rw-r--r--remoting/jingle_glue/server_log_entry.h64
-rw-r--r--remoting/jingle_glue/server_log_entry_unittest.cc57
-rw-r--r--remoting/jingle_glue/server_log_entry_unittest.h25
-rw-r--r--remoting/proto/control.proto4
-rw-r--r--remoting/protocol/connection_to_client.h4
-rw-r--r--remoting/protocol/connection_to_host.cc77
-rw-r--r--remoting/protocol/connection_to_host.h36
-rw-r--r--remoting/protocol/content_description.cc4
-rw-r--r--remoting/protocol/content_description.h2
-rw-r--r--remoting/protocol/content_description_unittest.cc6
-rw-r--r--remoting/protocol/jingle_session_unittest.cc2
-rw-r--r--remoting/protocol/libjingle_transport_factory.cc25
-rw-r--r--remoting/protocol/monitored_video_stub.h2
-rw-r--r--remoting/protocol/session_config.cc40
-rw-r--r--remoting/protocol/session_config.h51
-rw-r--r--remoting/protocol/ssl_hmac_channel_authenticator.cc22
-rw-r--r--remoting/remoting.gyp1
-rw-r--r--remoting/remoting_android.gypi5
-rw-r--r--remoting/remoting_client.gypi33
-rw-r--r--remoting/remoting_host.gypi8
-rw-r--r--remoting/remoting_nacl.gyp247
-rw-r--r--remoting/remoting_srcs.gypi10
-rw-r--r--remoting/remoting_test.gypi12
-rw-r--r--remoting/remoting_webapp.gypi11
-rw-r--r--remoting/remoting_webapp_files.gypi11
-rw-r--r--remoting/resources/drag.webpbin0 -> 148 bytes
-rw-r--r--remoting/resources/icon_close.webpbin0 -> 410 bytes
-rw-r--r--remoting/resources/icon_disconnect.webpbin0 -> 360 bytes
-rw-r--r--remoting/resources/icon_maximize_restore.webpbin0 -> 350 bytes
-rw-r--r--remoting/resources/icon_minimize.webpbin0 -> 176 bytes
-rw-r--r--remoting/resources/remoting_strings.grd17
-rw-r--r--remoting/resources/remoting_strings_ar.xtb9
-rw-r--r--remoting/resources/remoting_strings_bg.xtb9
-rw-r--r--remoting/resources/remoting_strings_ca.xtb11
-rw-r--r--remoting/resources/remoting_strings_cs.xtb11
-rw-r--r--remoting/resources/remoting_strings_da.xtb9
-rw-r--r--remoting/resources/remoting_strings_de.xtb9
-rw-r--r--remoting/resources/remoting_strings_el.xtb13
-rw-r--r--remoting/resources/remoting_strings_en-GB.xtb9
-rw-r--r--remoting/resources/remoting_strings_es-419.xtb9
-rw-r--r--remoting/resources/remoting_strings_es.xtb9
-rw-r--r--remoting/resources/remoting_strings_et.xtb11
-rw-r--r--remoting/resources/remoting_strings_fi.xtb9
-rw-r--r--remoting/resources/remoting_strings_fil.xtb9
-rw-r--r--remoting/resources/remoting_strings_fr.xtb9
-rw-r--r--remoting/resources/remoting_strings_hi.xtb9
-rw-r--r--remoting/resources/remoting_strings_hr.xtb9
-rw-r--r--remoting/resources/remoting_strings_hu.xtb9
-rw-r--r--remoting/resources/remoting_strings_id.xtb9
-rw-r--r--remoting/resources/remoting_strings_it.xtb9
-rw-r--r--remoting/resources/remoting_strings_iw.xtb9
-rw-r--r--remoting/resources/remoting_strings_ja.xtb9
-rw-r--r--remoting/resources/remoting_strings_ko.xtb9
-rw-r--r--remoting/resources/remoting_strings_lt.xtb9
-rw-r--r--remoting/resources/remoting_strings_lv.xtb9
-rw-r--r--remoting/resources/remoting_strings_nl.xtb9
-rw-r--r--remoting/resources/remoting_strings_no.xtb9
-rw-r--r--remoting/resources/remoting_strings_pl.xtb9
-rw-r--r--remoting/resources/remoting_strings_pt-BR.xtb9
-rw-r--r--remoting/resources/remoting_strings_pt-PT.xtb9
-rw-r--r--remoting/resources/remoting_strings_ro.xtb9
-rw-r--r--remoting/resources/remoting_strings_ru.xtb9
-rw-r--r--remoting/resources/remoting_strings_sk.xtb9
-rw-r--r--remoting/resources/remoting_strings_sl.xtb9
-rw-r--r--remoting/resources/remoting_strings_sr.xtb9
-rw-r--r--remoting/resources/remoting_strings_sv.xtb9
-rw-r--r--remoting/resources/remoting_strings_th.xtb9
-rw-r--r--remoting/resources/remoting_strings_tr.xtb9
-rw-r--r--remoting/resources/remoting_strings_uk.xtb9
-rw-r--r--remoting/resources/remoting_strings_vi.xtb9
-rw-r--r--remoting/resources/remoting_strings_zh-CN.xtb11
-rw-r--r--remoting/resources/remoting_strings_zh-TW.xtb11
-rw-r--r--remoting/tools/breakpad_tester_win.cc11
-rw-r--r--remoting/webapp/background.js3
-rw-r--r--remoting/webapp/base.js18
-rw-r--r--remoting/webapp/browser_test/browser_test.js134
-rw-r--r--remoting/webapp/browser_test/cancel_pin_browser_test.js46
-rw-r--r--remoting/webapp/browser_test/invalid_pin_browser_test.js44
-rw-r--r--remoting/webapp/browser_test/update_pin_browser_test.js109
-rwxr-xr-xremoting/webapp/build-webapp.py5
-rw-r--r--remoting/webapp/client_plugin.js9
-rw-r--r--remoting/webapp/client_session.js99
-rw-r--r--remoting/webapp/event_handlers.js6
-rw-r--r--remoting/webapp/fullscreen_v2.js2
-rw-r--r--remoting/webapp/host_controller.js7
-rw-r--r--remoting/webapp/host_dispatcher.js11
-rw-r--r--remoting/webapp/host_install_dialog.js68
-rw-r--r--remoting/webapp/host_it2me_dispatcher.js16
-rw-r--r--remoting/webapp/host_screen.js34
-rw-r--r--remoting/webapp/host_setup_dialog.js54
-rw-r--r--remoting/webapp/html/template_main.html92
-rw-r--r--remoting/webapp/html/toolbar.html12
-rw-r--r--remoting/webapp/html/window_frame.html29
-rw-r--r--remoting/webapp/js_proto/chrome_proto.js1
-rw-r--r--remoting/webapp/js_proto/dom_proto.js43
-rw-r--r--remoting/webapp/main.css4
-rw-r--r--remoting/webapp/manifest.json.jinja249
-rw-r--r--remoting/webapp/plugin_settings.js3
-rw-r--r--remoting/webapp/remoting.js11
-rw-r--r--remoting/webapp/remoting_client_pnacl.nmf10
-rw-r--r--remoting/webapp/smart_reconnector.js16
-rw-r--r--remoting/webapp/toolbar.css2
-rw-r--r--remoting/webapp/ui_mode.js9
-rw-r--r--remoting/webapp/window_frame.css174
-rw-r--r--remoting/webapp/window_frame.js175
274 files changed, 13316 insertions, 1868 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
index f8a9849bfe..dd9088807f 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
@@ -10,6 +10,7 @@ import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
+import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
import android.app.AlertDialog;
@@ -18,6 +19,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
+import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@@ -98,6 +100,7 @@ public class Chromoting extends Activity implements JniInterface.ConnectionListe
builder.setMessage(R.string.noaccounts_message);
builder.setPositiveButton(R.string.noaccounts_add_account,
new DialogInterface.OnClickListener() {
+ @SuppressLint("InlinedApi")
@Override
public void onClick(DialogInterface dialog, int id) {
Intent intent = new Intent(Settings.ACTION_ADD_ACCOUNT);
diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
index e4a8d0c8be..5ba3f013e1 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java
@@ -4,6 +4,7 @@
package org.chromium.chromoting;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Build;
@@ -79,10 +80,7 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
// IMMERSIVE_STICKY mode is used, the system clears this flag (leaving the FULLSCREEN flag
// set) when the user swipes the edge to reveal the bars temporarily. When this happens,
// the action-bar should remain hidden.
- int fullscreenFlags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- fullscreenFlags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
- }
+ int fullscreenFlags = getSystemUiFlags();
if ((visibility & fullscreenFlags) != 0) {
hideActionBar();
} else {
@@ -90,6 +88,15 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
}
}
+ @SuppressLint("InlinedApi")
+ private int getSystemUiFlags() {
+ int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
+ }
+ return flags;
+ }
+
public void showActionBar() {
mOverlayButton.setVisibility(View.INVISIBLE);
getActionBar().show();
@@ -106,10 +113,7 @@ public class Desktop extends Activity implements View.OnSystemUiVisibilityChange
// LOW_PROFILE gives the status and navigation bars a "lights-out" appearance.
// FULLSCREEN hides the status bar on supported devices (4.1 and above).
- int flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- flags |= View.SYSTEM_UI_FLAG_FULLSCREEN;
- }
+ int flags = getSystemUiFlags();
// HIDE_NAVIGATION hides the navigation bar. However, if the user touches the screen, the
// event is not seen by the application and instead the navigation bar is re-shown.
diff --git a/remoting/base/capabilities.cc b/remoting/base/capabilities.cc
index 33cf0eed7b..4e52ef8652 100644
--- a/remoting/base/capabilities.cc
+++ b/remoting/base/capabilities.cc
@@ -7,6 +7,7 @@
#include <algorithm>
#include <vector>
+#include "base/stl_util.h"
#include "base/strings/string_util.h"
namespace remoting {
@@ -27,16 +28,9 @@ std::string IntersectCapabilities(const std::string& client_capabilities,
Tokenize(host_capabilities, " ", &host_caps);
std::sort(host_caps.begin(), host_caps.end());
- std::vector<std::string> result(std::min(client_caps.size(),
- host_caps.size()));
- std::vector<std::string>::iterator end =
- std::set_intersection(client_caps.begin(),
- client_caps.end(),
- host_caps.begin(),
- host_caps.end(),
- result.begin());
- if (end != result.end())
- result.erase(end, result.end());
+ std::vector<std::string> result =
+ base::STLSetIntersection<std::vector<std::string> >(
+ client_caps, host_caps);
return JoinString(result, " ");
}
diff --git a/remoting/base/util.cc b/remoting/base/util.cc
index 8102cb9bf5..6ee8548f53 100644
--- a/remoting/base/util.cc
+++ b/remoting/base/util.cc
@@ -45,28 +45,6 @@ int CalculateUVOffset(int x, int y, int stride) {
return stride * y / 2 + x / 2;
}
-void ConvertRGB32ToYUVWithRect(const uint8* rgb_plane,
- uint8* y_plane,
- uint8* u_plane,
- uint8* v_plane,
- int x,
- int y,
- int width,
- int height,
- int rgb_stride,
- int y_stride,
- int uv_stride) {
- int rgb_offset = CalculateRGBOffset(x, y, rgb_stride);
- int y_offset = CalculateYOffset(x, y, y_stride);
- int uv_offset = CalculateUVOffset(x, y, uv_stride);;
-
- libyuv::ARGBToI420(rgb_plane + rgb_offset, rgb_stride,
- y_plane + y_offset, y_stride,
- u_plane + uv_offset, uv_stride,
- v_plane + uv_offset, uv_stride,
- width, height);
-}
-
void ConvertAndScaleYUVToRGB32Rect(
const uint8* source_yplane,
const uint8* source_uplane,
diff --git a/remoting/base/util.h b/remoting/base/util.h
index c8c70210f6..2546221ad4 100644
--- a/remoting/base/util.h
+++ b/remoting/base/util.h
@@ -50,19 +50,6 @@ void ConvertAndScaleYUVToRGB32Rect(
const webrtc::DesktopRect& dest_buffer_rect,
const webrtc::DesktopRect& dest_rect);
-// Convert RGB32 to YUV on a specific rectangle.
-void ConvertRGB32ToYUVWithRect(const uint8* rgb_plane,
- uint8* y_plane,
- uint8* u_plane,
- uint8* v_plane,
- int x,
- int y,
- int width,
- int height,
- int rgb_stride,
- int y_stride,
- int uv_stride);
-
int RoundToTwosMultiple(int x);
// Align the sides of the rectangle to multiples of 2 (expanding outwards).
diff --git a/remoting/base/util_unittest.cc b/remoting/base/util_unittest.cc
index 28fd1a77a3..42237f7ddf 100644
--- a/remoting/base/util_unittest.cc
+++ b/remoting/base/util_unittest.cc
@@ -6,6 +6,7 @@
#include "remoting/base/util.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libyuv/include/libyuv/convert_from_argb.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
static const int kWidth = 32 ;
@@ -91,17 +92,16 @@ class YuvToRgbTester {
FillRgbBuffer(rect);
// RGB -> YUV
- ConvertRGB32ToYUVWithRect(rgb_buffer_.get(),
- yplane_,
- uplane_,
- vplane_,
- 0,
- 0,
- kWidth,
- kHeight,
- kRgbStride,
- kYStride,
- kUvStride);
+ libyuv::ARGBToI420(rgb_buffer_.get(),
+ kRgbStride,
+ yplane_,
+ kYStride,
+ uplane_,
+ kUvStride,
+ vplane_,
+ kUvStride,
+ kWidth,
+ kHeight);
// Reset RGB buffer and do opposite conversion.
ResetRgbBuffer();
diff --git a/remoting/client/chromoting_client.cc b/remoting/client/chromoting_client.cc
index 40eddcd3ae..bc18249dca 100644
--- a/remoting/client/chromoting_client.cc
+++ b/remoting/client/chromoting_client.cc
@@ -66,16 +66,17 @@ void ChromotingClient::Start(
// Create a WeakPtr to ourself for to use for all posted tasks.
weak_ptr_ = weak_factory_.GetWeakPtr();
+ connection_->set_client_stub(this);
+ connection_->set_clipboard_stub(this);
+ connection_->set_video_stub(video_renderer_);
+ connection_->set_audio_stub(audio_decode_scheduler_.get());
+
connection_->Connect(signal_strategy,
- config_.host_jid,
- config_.host_public_key,
transport_factory.Pass(),
authenticator.Pass(),
- this,
- this,
- this,
- video_renderer_,
- audio_decode_scheduler_.get());
+ config_.host_jid,
+ config_.host_public_key,
+ this);
}
void ChromotingClient::SetCapabilities(
diff --git a/remoting/client/jni/chromoting_jni_instance.cc b/remoting/client/jni/chromoting_jni_instance.cc
index 3f9bd22fcd..5dd94b30b1 100644
--- a/remoting/client/jni/chromoting_jni_instance.cc
+++ b/remoting/client/jni/chromoting_jni_instance.cc
@@ -8,16 +8,17 @@
#include "base/bind.h"
#include "base/logging.h"
+#include "jingle/glue/thread_wrapper.h"
#include "net/socket/client_socket_factory.h"
#include "remoting/client/audio_player.h"
#include "remoting/client/jni/android_keymap.h"
#include "remoting/client/jni/chromoting_jni_runtime.h"
#include "remoting/client/log_to_server.h"
-#include "remoting/client/server_log_entry.h"
#include "remoting/client/software_video_renderer.h"
#include "remoting/jingle_glue/chromium_port_allocator.h"
#include "remoting/jingle_glue/chromium_socket_factory.h"
#include "remoting/jingle_glue/network_settings.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/libjingle_transport_factory.h"
@@ -314,6 +315,8 @@ void ChromotingJniInstance::ConnectToHostOnDisplayThread() {
void ChromotingJniInstance::ConnectToHostOnNetworkThread() {
DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
+
client_context_.reset(new ClientContext(
jni_runtime_->network_task_runner().get()));
client_context_->Start();
@@ -336,7 +339,7 @@ void ChromotingJniInstance::ConnectToHostOnNetworkThread() {
net::ClientSocketFactory::GetDefaultFactory(),
jni_runtime_->url_requester(), xmpp_config_));
- log_to_server_.reset(new client::LogToServer(client::ServerLogEntry::ME2ME,
+ log_to_server_.reset(new client::LogToServer(ServerLogEntry::ME2ME,
signaling_.get(),
"remoting@bot.talk.google.com"));
diff --git a/remoting/client/jni/chromoting_jni_runtime.cc b/remoting/client/jni/chromoting_jni_runtime.cc
index 467213ee94..8384d67999 100644
--- a/remoting/client/jni/chromoting_jni_runtime.cc
+++ b/remoting/client/jni/chromoting_jni_runtime.cc
@@ -46,7 +46,7 @@ static void LoadNative(JNIEnv* env, jclass clazz, jobject context) {
// runtime API keys have been specified by the environment. Unfortunately, we
// neither launch Chromium nor have a command line, so we need to prevent
// them from DCHECKing out when they go looking.
- CommandLine::Init(0, NULL);
+ base::CommandLine::Init(0, NULL);
// Create the singleton now so that the Chromoting threads will be set up.
remoting::ChromotingJniRuntime::GetInstance();
diff --git a/remoting/client/log_to_server.cc b/remoting/client/log_to_server.cc
index c45ad386cb..e29de13584 100644
--- a/remoting/client/log_to_server.cc
+++ b/remoting/client/log_to_server.cc
@@ -8,6 +8,7 @@
#include "base/rand_util.h"
#include "remoting/base/constants.h"
#include "remoting/client/chromoting_stats.h"
+#include "remoting/client/server_log_entry_client.h"
#include "remoting/jingle_glue/iq_sender.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
@@ -69,8 +70,8 @@ void LogToServer::LogSessionStateChange(
DCHECK(CalledOnValidThread());
scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(state, error));
- entry->AddClientFields();
+ MakeLogEntryForSessionStateChange(state, error));
+ AddClientFieldsToLogEntry(entry.get());
entry->AddModeField(mode_);
MaybeExpireSessionId();
@@ -85,12 +86,13 @@ void LogToServer::LogSessionStateChange(
}
if (!session_id_.empty()) {
- entry->AddSessionId(session_id_);
+ AddSessionIdToLogEntry(entry.get(), session_id_);
}
// Maybe clear the session start time and log the session duration.
if (ShouldAddDuration(state) && !session_start_time_.is_null()) {
- entry->AddSessionDuration(base::TimeTicks::Now() - session_start_time_);
+ AddSessionDurationToLogEntry(entry.get(),
+ base::TimeTicks::Now() - session_start_time_);
}
if (IsEndOfSession(state)) {
@@ -106,11 +108,10 @@ void LogToServer::LogStatistics(ChromotingStats* statistics) {
MaybeExpireSessionId();
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForStatistics(statistics));
- entry->AddClientFields();
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForStatistics(statistics));
+ AddClientFieldsToLogEntry(entry.get());
entry->AddModeField(mode_);
- entry->AddSessionId(session_id_);
+ AddSessionIdToLogEntry(entry.get(), session_id_);
Log(*entry.get());
}
@@ -174,8 +175,7 @@ void LogToServer::MaybeExpireSessionId() {
base::TimeDelta max_age = base::TimeDelta::FromDays(kMaxSessionIdAgeDays);
if (base::TimeTicks::Now() - session_id_generation_time_ > max_age) {
// Log the old session ID.
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionIdOld(session_id_));
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionIdOld(session_id_));
entry->AddModeField(mode_);
Log(*entry.get());
@@ -183,7 +183,7 @@ void LogToServer::MaybeExpireSessionId() {
GenerateSessionId();
// Log the new session ID.
- entry = ServerLogEntry::MakeForSessionIdNew(session_id_);
+ entry = MakeLogEntryForSessionIdNew(session_id_);
entry->AddModeField(mode_);
Log(*entry.get());
}
diff --git a/remoting/client/log_to_server.h b/remoting/client/log_to_server.h
index 35f3c96f04..cd70ff5384 100644
--- a/remoting/client/log_to_server.h
+++ b/remoting/client/log_to_server.h
@@ -11,7 +11,7 @@
#include "base/threading/non_thread_safe.h"
#include "base/time/time.h"
-#include "remoting/client/server_log_entry.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "remoting/protocol/connection_to_host.h"
#include "remoting/protocol/errors.h"
diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc
index 3504c71fcb..5020be775d 100644
--- a/remoting/client/plugin/chromoting_instance.cc
+++ b/remoting/client/plugin/chromoting_instance.cc
@@ -8,6 +8,11 @@
#include <string>
#include <vector>
+#if defined(OS_NACL)
+#include <sys/mount.h>
+#include <nacl_io/nacl_io.h>
+#endif
+
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
@@ -21,7 +26,7 @@
#include "base/values.h"
#include "crypto/random.h"
#include "jingle/glue/thread_wrapper.h"
-#include "media/base/media.h"
+#include "media/base/yuv_convert.h"
#include "net/socket/ssl_server_socket.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/dev/url_util_dev.h"
@@ -37,6 +42,8 @@
#include "remoting/client/frame_consumer_proxy.h"
#include "remoting/client/plugin/delegating_signal_strategy.h"
#include "remoting/client/plugin/media_source_video_renderer.h"
+#include "remoting/client/plugin/normalizing_input_filter_cros.h"
+#include "remoting/client/plugin/normalizing_input_filter_mac.h"
#include "remoting/client/plugin/pepper_audio_player.h"
#include "remoting/client/plugin/pepper_input_handler.h"
#include "remoting/client/plugin/pepper_port_allocator.h"
@@ -47,6 +54,7 @@
#include "remoting/protocol/host_stub.h"
#include "remoting/protocol/libjingle_transport_factory.h"
#include "third_party/libjingle/source/talk/base/helpers.h"
+#include "third_party/libjingle/source/talk/base/ssladapter.h"
#include "url/gurl.h"
// Windows defines 'PostMessage', so we have to undef it.
@@ -205,11 +213,28 @@ ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
context_(plugin_task_runner_.get()),
input_tracker_(&mouse_input_filter_),
key_mapper_(&input_tracker_),
- normalizing_input_filter_(CreateNormalizingInputFilter(&key_mapper_)),
- input_handler_(this, normalizing_input_filter_.get()),
+ input_handler_(this),
use_async_pin_dialog_(false),
use_media_source_rendering_(false),
weak_factory_(this) {
+#if defined(OS_NACL)
+ // In NaCl global resources need to be initialized differently because they
+ // are not shared with Chrome.
+ thread_task_runner_handle_.reset(
+ new base::ThreadTaskRunnerHandle(plugin_task_runner_));
+ thread_wrapper_.reset(
+ new jingle_glue::JingleThreadWrapper(plugin_task_runner_));
+ media::InitializeCPUSpecificYUVConversions();
+#else
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
+#endif
+
+#if defined(OS_NACL)
+ nacl_io_init_ppapi(pp_instance, pp::Module::Get()->get_browser_interface());
+ mount("", "/etc", "memfs", 0, "");
+ mount("", "/usr", "memfs", 0, "");
+#endif
+
RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL);
RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD);
@@ -221,7 +246,12 @@ ChromotingInstance::ChromotingInstance(PP_Instance pp_instance)
char random_seed[kRandomSeedSize];
crypto::RandBytes(random_seed, sizeof(random_seed));
talk_base::InitRandom(random_seed, sizeof(random_seed));
-#endif // defined(USE_OPENSSL)
+#else
+ // Libjingle's SSL implementation is not really used, but it has to be
+ // initialized for NSS builds to make sure that RNG is initialized in NSS,
+ // because libjingle uses it.
+ talk_base::InitializeSSL();
+#endif // !defined(USE_OPENSSL)
// Send hello message.
scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue());
@@ -264,18 +294,15 @@ bool ChromotingInstance::Init(uint32_t argc,
VLOG(1) << "Started ChromotingInstance::Init";
- // Check to make sure the media library is initialized.
- // http://crbug.com/91521.
- if (!media::IsMediaLibraryInitialized()) {
- LOG(ERROR) << "Media library not initialized.";
- return false;
- }
-
- // Check that the calling content is part of an app or extension.
+ // Check that the calling content is part of an app or extension. This is only
+ // necessary for non-PNaCl version of the plugin. Also PPB_URLUtil_Dev doesn't
+ // work in NaCl at the moment so the check fails in NaCl builds.
+#if !defined(OS_NACL)
if (!IsCallerAppOrExtension()) {
LOG(ERROR) << "Not an app or extension";
return false;
}
+#endif
// Start all the threads.
context_.Start();
@@ -339,12 +366,17 @@ void ChromotingInstance::HandleMessage(const pp::Var& message) {
HandleAllowMouseLockMessage();
} else if (method == "enableMediaSourceRendering") {
HandleEnableMediaSourceRendering();
+ } else if (method == "sendMouseInputWhenUnfocused") {
+ HandleSendMouseInputWhenUnfocused();
}
}
void ChromotingInstance::DidChangeFocus(bool has_focus) {
DCHECK(plugin_task_runner_->BelongsToCurrentThread());
+ if (!IsConnected())
+ return;
+
input_handler_.DidChangeFocus(has_focus);
}
@@ -620,6 +652,30 @@ void ChromotingInstance::HandleConnect(const base::DictionaryValue& data) {
}
}
+#if defined(OS_NACL)
+ std::string key_filter;
+ if (!data.GetString("keyFilter", &key_filter)) {
+ NOTREACHED();
+ normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
+ } else if (key_filter == "mac") {
+ normalizing_input_filter_.reset(
+ new NormalizingInputFilterMac(&key_mapper_));
+ } else if (key_filter == "cros") {
+ normalizing_input_filter_.reset(
+ new NormalizingInputFilterCros(&key_mapper_));
+ } else {
+ DCHECK(key_filter.empty());
+ normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
+ }
+#elif defined(OS_MACOSX)
+ normalizing_input_filter_.reset(new NormalizingInputFilterMac(&key_mapper_));
+#elif defined(OS_CHROMEOS)
+ normalizing_input_filter_.reset(new NormalizingInputFilterCros(&key_mapper_));
+#else
+ normalizing_input_filter_.reset(new protocol::InputFilter(&key_mapper_));
+#endif
+ input_handler_.set_input_stub(normalizing_input_filter_.get());
+
ConnectWithConfig(config, local_jid);
}
@@ -627,8 +683,6 @@ void ChromotingInstance::ConnectWithConfig(const ClientConfig& config,
const std::string& local_jid) {
DCHECK(plugin_task_runner_->BelongsToCurrentThread());
- jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
-
if (use_media_source_rendering_) {
video_renderer_.reset(new MediaSourceVideoRenderer(this));
} else {
@@ -783,7 +837,7 @@ void ChromotingInstance::HandleSendClipboardItem(
protocol::ClipboardEvent event;
event.set_mime_type(mime_type);
event.set_data(item);
- host_connection_->clipboard_stub()->InjectClipboardEvent(event);
+ host_connection_->clipboard_forwarder()->InjectClipboardEvent(event);
}
void ChromotingInstance::HandleNotifyClientResolution(
@@ -918,6 +972,10 @@ void ChromotingInstance::HandleEnableMediaSourceRendering() {
use_media_source_rendering_ = true;
}
+void ChromotingInstance::HandleSendMouseInputWhenUnfocused() {
+ input_handler_.set_send_mouse_input_when_unfocused(true);
+}
+
ChromotingStats* ChromotingInstance::GetStats() {
if (!video_renderer_.get())
return NULL;
diff --git a/remoting/client/plugin/chromoting_instance.h b/remoting/client/plugin/chromoting_instance.h
index 0ed6d3de8a..840a8f7456 100644
--- a/remoting/client/plugin/chromoting_instance.h
+++ b/remoting/client/plugin/chromoting_instance.h
@@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(ajwong): We need to come up with a better description of the
-// responsibilities for each thread.
-
#ifndef REMOTING_CLIENT_PLUGIN_CHROMOTING_INSTANCE_H_
#define REMOTING_CLIENT_PLUGIN_CHROMOTING_INSTANCE_H_
@@ -13,6 +10,7 @@
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
+#include "base/thread_task_runner_handle.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_rect.h"
#include "ppapi/c/pp_resource.h"
@@ -22,7 +20,6 @@
#include "remoting/client/client_user_interface.h"
#include "remoting/client/key_event_mapper.h"
#include "remoting/client/plugin/media_source_video_renderer.h"
-#include "remoting/client/plugin/normalizing_input_filter.h"
#include "remoting/client/plugin/pepper_input_handler.h"
#include "remoting/client/plugin/pepper_plugin_thread_delegate.h"
#include "remoting/proto/event.pb.h"
@@ -45,6 +42,10 @@ class Module;
class VarDictionary;
} // namespace pp
+namespace jingle_glue {
+class JingleThreadWrapper;
+} // namespace jingle_glue
+
namespace webrtc {
class DesktopRegion;
class DesktopSize;
@@ -208,6 +209,7 @@ class ChromotingInstance :
void HandleExtensionMessage(const base::DictionaryValue& data);
void HandleAllowMouseLockMessage();
void HandleEnableMediaSourceRendering();
+ void HandleSendMouseInputWhenUnfocused();
// Helper method called from Connect() to connect with parsed config.
void ConnectWithConfig(const ClientConfig& config,
@@ -259,6 +261,8 @@ class ChromotingInstance :
PepperPluginThreadDelegate plugin_thread_delegate_;
scoped_refptr<PluginThreadTaskRunner> plugin_task_runner_;
+ scoped_ptr<base::ThreadTaskRunnerHandle> thread_task_runner_handle_;
+ scoped_ptr<jingle_glue::JingleThreadWrapper> thread_wrapper_;
ClientContext context_;
scoped_ptr<VideoRenderer> video_renderer_;
scoped_ptr<PepperView> view_;
diff --git a/remoting/client/plugin/media_source_video_renderer.cc b/remoting/client/plugin/media_source_video_renderer.cc
index 5326754ac9..817df11c7a 100644
--- a/remoting/client/plugin/media_source_video_renderer.cc
+++ b/remoting/client/plugin/media_source_video_renderer.cc
@@ -65,9 +65,25 @@ MediaSourceVideoRenderer::VideoWriter::VideoWriter(
.InMicroseconds() *
base::Time::kNanosecondsPerMicrosecond);
- segment_->AddVideoTrack(frame_size_.width(), frame_size_.height(), 1);
+ uint64 crop_right = 0;
+ int width = frame_size_.width();
+ if (width % 2 == 1) {
+ ++width;
+ crop_right = 1;
+ }
+
+ uint64 crop_bottom = 0;
+ int height = frame_size_.height();
+ if (height % 2 == 1) {
+ ++height;
+ crop_bottom = 1;
+ }
+
+ segment_->AddVideoTrack(width, height, 1);
mkvmuxer::VideoTrack* video_track =
reinterpret_cast<mkvmuxer::VideoTrack*>(segment_->GetTrackByNumber(1));
+ video_track->set_crop_right(crop_right);
+ video_track->set_crop_bottom(crop_bottom);
video_track->set_frame_rate(base::Time::kNanosecondsPerSecond /
kFrameIntervalNs);
video_track->set_default_duration(base::Time::kNanosecondsPerSecond);
diff --git a/remoting/client/plugin/normalizing_input_filter.cc b/remoting/client/plugin/normalizing_input_filter.cc
deleted file mode 100644
index 72063056db..0000000000
--- a/remoting/client/plugin/normalizing_input_filter.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "remoting/client/plugin/normalizing_input_filter.h"
-
-#include "remoting/protocol/input_filter.h"
-
-namespace remoting {
-
-using protocol::InputFilter;
-using protocol::InputStub;
-
-#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
-scoped_ptr<InputFilter> CreateNormalizingInputFilter(InputStub* input_stub) {
- return scoped_ptr<InputFilter>(new InputFilter(input_stub));
-}
-#endif // !defined(OS_MACOSX) && !defined(OS_CHROMEOS)
-
-}
diff --git a/remoting/client/plugin/normalizing_input_filter.h b/remoting/client/plugin/normalizing_input_filter.h
deleted file mode 100644
index ebe49fe4bc..0000000000
--- a/remoting/client/plugin/normalizing_input_filter.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_
-#define REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "remoting/protocol/input_filter.h"
-
-namespace remoting {
-
-// Returns an InputFilter which re-writes input events to work around
-// platform-specific behaviours. If no re-writing is required then a
-// pass-through InputFilter is returned.
-scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter(
- protocol::InputStub* input_stub);
-
-} // namespace remoting
-
-#endif // REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_
diff --git a/remoting/client/plugin/normalizing_input_filter_cros.cc b/remoting/client/plugin/normalizing_input_filter_cros.cc
index 81cec8d781..5ad77eee40 100644
--- a/remoting/client/plugin/normalizing_input_filter_cros.cc
+++ b/remoting/client/plugin/normalizing_input_filter_cros.cc
@@ -2,26 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// NormalizingInputFilterCros addresses the problems generated by key rewritings
-// such as Down->PageDown, 1->F1, etc, when keys are pressed in combination with
-// the OSKey (aka Search). Rewriting OSKey+Down, for example, causes us to
-// receive the following:
-//
-// keydown OSKey
-// keydown PageDown
-// keyup PageDown
-// keyup OSKey
-//
-// The host system will therefore behave as if OSKey+PageDown were pressed,
-// rather than PageDown alone.
-//
-// This file must be kept up-to-date with changes to
-// chrome/browser/ui/ash/event_rewriter.cc
-
-#include "remoting/client/plugin/normalizing_input_filter.h"
+#include "remoting/client/plugin/normalizing_input_filter_cros.h"
#include "base/logging.h"
-#include "remoting/proto/event.pb.h"
namespace remoting {
@@ -53,6 +36,8 @@ static bool IsRewrittenKey(unsigned int code) {
return IsRewrittenExtendedKey(code) || IsRewrittenFunctionKey(code);
}
+} // namespace
+
// The input filter tries to avoid sending keydown/keyup events for OSKey
// (aka Search, WinKey, Cmd, Super) when it is used to rewrite other key events.
// Rewriting via other combinations is not currently handled.
@@ -86,121 +71,102 @@ static bool IsRewrittenKey(unsigned int code) {
// 4. An OSKey is pressed, and is Modifying.
// - If the OSKey keyup is received then we send it and we move to State #1.
// - All other key event pass through the filter unchanged.
+//
+// This file must be kept up-to-date with changes to
+// chrome/browser/ui/ash/event_rewriter.cc
-class NormalizingInputFilterCros : public protocol::InputFilter {
- public:
- explicit NormalizingInputFilterCros(protocol::InputStub* input_stub)
- : protocol::InputFilter(input_stub),
- deferred_key_is_rewriting_(false),
- modifying_key_(0) {
- }
- virtual ~NormalizingInputFilterCros() {}
+NormalizingInputFilterCros::NormalizingInputFilterCros(
+ protocol::InputStub* input_stub)
+ : protocol::InputFilter(input_stub),
+ deferred_key_is_rewriting_(false),
+ modifying_key_(0) {
+}
- // InputFilter overrides.
- virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE {
- DCHECK(event.has_usb_keycode());
- DCHECK(event.has_pressed());
+NormalizingInputFilterCros::~NormalizingInputFilterCros() {}
- if (event.pressed())
- ProcessKeyDown(event);
- else
- ProcessKeyUp(event);
- }
+void NormalizingInputFilterCros::InjectKeyEvent(
+ const protocol::KeyEvent& event) {
+ DCHECK(event.has_usb_keycode());
+ DCHECK(event.has_pressed());
- virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE {
- if (deferred_keydown_event_.has_usb_keycode())
- SwitchRewritingKeyToModifying();
- InputFilter::InjectMouseEvent(event);
+ if (event.pressed()) {
+ ProcessKeyDown(event);
+ } else {
+ ProcessKeyUp(event);
}
+}
- private:
- void ProcessKeyDown(const protocol::KeyEvent& event) {
- // If |event| is |deferred_keydown_event_| auto-repeat then assume
- // that the user is holding the key down rather than using it to Rewrite.
- if (deferred_keydown_event_.has_usb_keycode() &&
- deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
- SwitchRewritingKeyToModifying();
- }
-
- // If |event| is a |modifying_key_| repeat then let it pass through.
- if (modifying_key_ == event.usb_keycode()) {
- InputFilter::InjectKeyEvent(event);
- return;
- }
-
- // If |event| is for an OSKey and we don't know whether it's a Normal,
- // Rewriting or Modifying use, then hold the keydown event.
- if (IsOsKey(event.usb_keycode())) {
- deferred_keydown_event_ = event;
- deferred_key_is_rewriting_ = false;
- return;
- }
+void NormalizingInputFilterCros::InjectMouseEvent(
+ const protocol::MouseEvent& event) {
+ if (deferred_keydown_event_.has_usb_keycode())
+ SwitchRewritingKeyToModifying();
+ InputFilter::InjectMouseEvent(event);
+}
- // If |event| is for a Rewritten key then set a flag to prevent any deferred
- // OSKey keydown from being sent when keyup is received for it. Otherwise,
- // inject the deferred OSKey keydown, if any, and switch that key into
- // Modifying mode.
- if (IsRewrittenKey(event.usb_keycode())) {
- // Note that there may not be a deferred OSKey event if there is a full
- // PC keyboard connected, which can generate e.g. PageDown without
- // rewriting.
- deferred_key_is_rewriting_ = true;
- } else {
- if (deferred_keydown_event_.has_usb_keycode())
- SwitchRewritingKeyToModifying();
- }
+void NormalizingInputFilterCros::ProcessKeyDown(
+ const protocol::KeyEvent& event) {
+ // If |event| is |deferred_keydown_event_| repeat then assume that the user is
+ // holding the key down rather than using it to Rewrite.
+ if (deferred_keydown_event_.has_usb_keycode() &&
+ deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
+ SwitchRewritingKeyToModifying();
+ }
+ // If |event| is a |modifying_key_| repeat then let it pass through.
+ if (modifying_key_ == event.usb_keycode()) {
InputFilter::InjectKeyEvent(event);
+ return;
}
- void ProcessKeyUp(const protocol::KeyEvent& event) {
- if (deferred_keydown_event_.has_usb_keycode() &&
- deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
- if (deferred_key_is_rewriting_) {
- // If we never sent the keydown then don't send a keyup.
- deferred_keydown_event_ = protocol::KeyEvent();
- return;
- }
-
- // If the OSKey hasn't Rewritten anything then treat as Modifying.
- SwitchRewritingKeyToModifying();
- }
-
- if (modifying_key_ == event.usb_keycode())
- modifying_key_ = 0;
-
- InputFilter::InjectKeyEvent(event);
+ // If |event| is for an OSKey and we don't know whether it's a Normal,
+ // Rewriting or Modifying use, then hold the keydown event.
+ if (IsOsKey(event.usb_keycode())) {
+ deferred_keydown_event_ = event;
+ deferred_key_is_rewriting_ = false;
+ return;
}
- void SwitchRewritingKeyToModifying() {
- DCHECK(deferred_keydown_event_.has_usb_keycode());
- modifying_key_ = deferred_keydown_event_.usb_keycode();
- InputFilter::InjectKeyEvent(deferred_keydown_event_);
- deferred_keydown_event_ = protocol::KeyEvent();
+ // If |event| is for a Rewritten key then set a flag to prevent any deferred
+ // OSKey keydown from being sent when keyup is received for it. Otherwise,
+ // inject the deferred OSKey keydown, if any, and switch that key into
+ // Modifying mode.
+ if (IsRewrittenKey(event.usb_keycode())) {
+ // Note that there may not be a deferred OSKey event if there is a full
+ // PC keyboard connected, which can generate e.g. PageDown without
+ // rewriting.
+ deferred_key_is_rewriting_ = true;
+ } else {
+ if (deferred_keydown_event_.has_usb_keycode())
+ SwitchRewritingKeyToModifying();
}
- // Holds the keydown event for the most recent OSKey to have been pressed,
- // while it is Rewriting, or we are not yet sure whether it is Normal,
- // Rewriting or Modifying. The event is sent on if we switch to Modifying, or
- // discarded if the OSKey is released while in Rewriting mode.
- protocol::KeyEvent deferred_keydown_event_;
+ InputFilter::InjectKeyEvent(event);
+}
- // True while the |rewrite_keydown_event_| key is Rewriting, i.e. was followed
- // by one or more Rewritten key events, and not by any Modified events.
- bool deferred_key_is_rewriting_;
+void NormalizingInputFilterCros::ProcessKeyUp(const protocol::KeyEvent& event) {
+ if (deferred_keydown_event_.has_usb_keycode() &&
+ deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
+ if (deferred_key_is_rewriting_) {
+ // If we never sent the keydown then don't send a keyup.
+ deferred_keydown_event_ = protocol::KeyEvent();
+ return;
+ }
- // Stores the code of the OSKey while it is pressed for use as a Modifier.
- uint32 modifying_key_;
+ // If the OSKey hasn't Rewritten anything then treat as Modifying.
+ SwitchRewritingKeyToModifying();
+ }
- DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterCros);
-};
+ if (modifying_key_ == event.usb_keycode())
+ modifying_key_ = 0;
-} // namespace
+ InputFilter::InjectKeyEvent(event);
+}
-scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter(
- protocol::InputStub* input_stub) {
- return scoped_ptr<protocol::InputFilter>(
- new NormalizingInputFilterCros(input_stub));
+void NormalizingInputFilterCros::SwitchRewritingKeyToModifying() {
+ DCHECK(deferred_keydown_event_.has_usb_keycode());
+ modifying_key_ = deferred_keydown_event_.usb_keycode();
+ InputFilter::InjectKeyEvent(deferred_keydown_event_);
+ deferred_keydown_event_ = protocol::KeyEvent();
}
} // namespace remoting
diff --git a/remoting/client/plugin/normalizing_input_filter_cros.h b/remoting/client/plugin/normalizing_input_filter_cros.h
new file mode 100644
index 0000000000..977c90caed
--- /dev/null
+++ b/remoting/client/plugin/normalizing_input_filter_cros.h
@@ -0,0 +1,54 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/protocol/input_filter.h"
+
+#include "remoting/proto/event.pb.h"
+
+namespace remoting {
+
+// NormalizingInputFilterCros addresses the problems generated by key rewritings
+// such as Down->PageDown, 1->F1, etc, when keys are pressed in combination with
+// the OSKey (aka Search). Rewriting OSKey+Down, for example, causes us to
+// receive the following:
+//
+// keydown OSKey
+// keydown PageDown
+// keyup PageDown
+// keyup OSKey
+//
+// The host system will therefore behave as if OSKey+PageDown were pressed,
+// rather than PageDown alone.
+class NormalizingInputFilterCros : public protocol::InputFilter {
+ public:
+ explicit NormalizingInputFilterCros(protocol::InputStub* input_stub);
+ virtual ~NormalizingInputFilterCros();
+
+ // InputFilter overrides.
+ virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE;
+ virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE;
+
+ private:
+ void ProcessKeyDown(const protocol::KeyEvent& event);
+ void ProcessKeyUp(const protocol::KeyEvent& event);
+
+ void SwitchRewritingKeyToModifying();
+
+ // Holds the keydown event for the most recent OSKey to have been pressed,
+ // while it is Rewriting, or we are not yet sure whether it is Normal,
+ // Rewriting or Modifying. The event is sent on if we switch to Modifying, or
+ // discarded if the OSKey is released while in Rewriting mode.
+ protocol::KeyEvent deferred_keydown_event_;
+
+ // True while the |rewrite_keydown_event_| key is Rewriting, i.e. was followed
+ // by one or more Rewritten key events, and not by any Modified events.
+ bool deferred_key_is_rewriting_;
+
+ // Stores the code of the OSKey while it is pressed for use as a Modifier.
+ uint32 modifying_key_;
+
+ DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterCros);
+};
+
+} // namespace remoting
diff --git a/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc b/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc
index cf46d1633c..900147150b 100644
--- a/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc
+++ b/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "remoting/client/plugin/normalizing_input_filter.h"
+#include "remoting/client/plugin/normalizing_input_filter_cros.h"
+
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -21,9 +22,9 @@ namespace {
const unsigned int kUsbLeftOsKey = 0x0700e3;
const unsigned int kUsbRightOsKey = 0x0700e7;
-const unsigned int kUsbFunctionKey = 0x07003a; // F1
-const unsigned int kUsbExtendedKey = 0x070049; // Insert
-const unsigned int kUsbOtherKey = 0x07002b; // Tab
+const unsigned int kUsbFunctionKey = 0x07003a; // F1
+const unsigned int kUsbExtendedKey = 0x070049; // Insert
+const unsigned int kUsbOtherKey = 0x07002b; // Tab
// A hardcoded value used to verify |lock_states| is preserved.
static const uint32 kTestLockStates = protocol::KeyEvent::LOCK_STATES_NUMLOCK;
@@ -63,8 +64,8 @@ static MouseEvent MakeMouseMoveEvent(int x, int y) {
// Test OSKey press/release.
TEST(NormalizingInputFilterCrosTest, PressReleaseOsKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -84,8 +85,8 @@ TEST(NormalizingInputFilterCrosTest, PressReleaseOsKey) {
// Test OSKey key repeat switches it to "modifying" mode.
TEST(NormalizingInputFilterCrosTest, OSKeyRepeats) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -106,8 +107,8 @@ TEST(NormalizingInputFilterCrosTest, OSKeyRepeats) {
// just the function key events.
TEST(NormalizingInputFilterCrosTest, FunctionKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -126,8 +127,8 @@ TEST(NormalizingInputFilterCrosTest, FunctionKey) {
// just the function key events.
TEST(NormalizingInputFilterCrosTest, ExtendedKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -146,8 +147,8 @@ TEST(NormalizingInputFilterCrosTest, ExtendedKey) {
// results in normal-looking sequence.
TEST(NormalizingInputFilterCrosTest, OtherKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -168,8 +169,8 @@ TEST(NormalizingInputFilterCrosTest, OtherKey) {
// results in OSKey switching to modifying mode for the normal key.
TEST(NormalizingInputFilterCrosTest, ExtendedThenOtherKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
@@ -192,8 +193,8 @@ TEST(NormalizingInputFilterCrosTest, ExtendedThenOtherKey) {
// Test OSKey press followed by mouse event puts the OSKey into modifying mode.
TEST(NormalizingInputFilterCrosTest, MouseEvent) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterCros(&stub));
{
InSequence s;
diff --git a/remoting/client/plugin/normalizing_input_filter_mac.cc b/remoting/client/plugin/normalizing_input_filter_mac.cc
index 56b327eefc..36708e9409 100644
--- a/remoting/client/plugin/normalizing_input_filter_mac.cc
+++ b/remoting/client/plugin/normalizing_input_filter_mac.cc
@@ -2,42 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// NormalizingInputFilterMac is designed to solve the problem of missing keyup
-// events on Mac.
-//
-// PROBLEM
-//
-// On Mac if user presses CMD and then C key there is no keyup event generated
-// for C when user releases the C key before the CMD key.
-// The cause is that CMD + C triggers a system action and Chrome injects only a
-// keydown event for the C key. Safari shares the same behavior.
-//
-// SOLUTION
-//
-// When a keyup event for CMD key happens we will check all prior keydown
-// events received and inject corresponding keyup events artificially, with
-// the exception of:
-//
-// SHIFT, CONTROL, OPTION, LEFT CMD, RIGHT CMD and CAPS LOCK
-//
-// because they are reported by Chrome correctly.
-//
-// There are a couple cases that this solution doesn't work perfectly, one
-// of them leads to duplicated keyup events.
-//
-// User performs this sequence of actions:
-//
-// CMD DOWN, C DOWN, CMD UP, C UP
-//
-// In this case the algorithm will generate:
-//
-// CMD DOWN, C DOWN, C UP, CMD UP, C UP
-//
-// Because we artificially generate keyup events the C UP event is duplicated
-// as user releases the key after CMD key. This would not be a problem as the
-// receiver end will drop this duplicated keyup event.
-
-#include "remoting/client/plugin/normalizing_input_filter.h"
+#include "remoting/client/plugin/normalizing_input_filter_mac.h"
#include <map>
#include <vector>
@@ -62,30 +27,13 @@ const unsigned int kUsbTab = 0x07002b;
} // namespace
-class NormalizingInputFilterMac : public protocol::InputFilter {
- public:
- explicit NormalizingInputFilterMac(protocol::InputStub* input_stub);
- virtual ~NormalizingInputFilterMac() {}
-
- // InputFilter overrides.
- virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE;
-
- private:
- // Generate keyup events for any keys pressed with CMD.
- void GenerateKeyupEvents();
-
- // A map that stores pressed keycodes and the corresponding key event.
- typedef std::map<int, protocol::KeyEvent> KeyPressedMap;
- KeyPressedMap key_pressed_map_;
-
- DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterMac);
-};
-
NormalizingInputFilterMac::NormalizingInputFilterMac(
protocol::InputStub* input_stub)
: protocol::InputFilter(input_stub) {
}
+NormalizingInputFilterMac::~NormalizingInputFilterMac() {}
+
void NormalizingInputFilterMac::InjectKeyEvent(const protocol::KeyEvent& event)
{
DCHECK(event.has_usb_keycode());
@@ -145,10 +93,4 @@ void NormalizingInputFilterMac::GenerateKeyupEvents() {
key_pressed_map_.clear();
}
-scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter(
- protocol::InputStub* input_stub) {
- return scoped_ptr<protocol::InputFilter>(
- new NormalizingInputFilterMac(input_stub));
-}
-
} // namespace remoting
diff --git a/remoting/client/plugin/normalizing_input_filter_mac.h b/remoting/client/plugin/normalizing_input_filter_mac.h
new file mode 100644
index 0000000000..ed1d0d3c43
--- /dev/null
+++ b/remoting/client/plugin/normalizing_input_filter_mac.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+
+#include "remoting/protocol/input_filter.h"
+
+namespace remoting {
+
+// NormalizingInputFilterMac is designed to solve the problem of missing keyup
+// events on Mac.
+//
+// PROBLEM
+//
+// On Mac if user presses CMD and then C key there is no keyup event generated
+// for C when user releases the C key before the CMD key.
+// The cause is that CMD + C triggers a system action and Chrome injects only a
+// keydown event for the C key. Safari shares the same behavior.
+//
+// SOLUTION
+//
+// When a keyup event for CMD key happens we will check all prior keydown
+// events received and inject corresponding keyup events artificially, with
+// the exception of:
+//
+// SHIFT, CONTROL, OPTION, LEFT CMD, RIGHT CMD and CAPS LOCK
+//
+// because they are reported by Chrome correctly.
+//
+// There are a couple cases that this solution doesn't work perfectly, one
+// of them leads to duplicated keyup events.
+//
+// User performs this sequence of actions:
+//
+// CMD DOWN, C DOWN, CMD UP, C UP
+//
+// In this case the algorithm will generate:
+//
+// CMD DOWN, C DOWN, C UP, CMD UP, C UP
+//
+// Because we artificially generate keyup events the C UP event is duplicated
+// as user releases the key after CMD key. This would not be a problem as the
+// receiver end will drop this duplicated keyup event.
+class NormalizingInputFilterMac : public protocol::InputFilter {
+ public:
+ explicit NormalizingInputFilterMac(protocol::InputStub* input_stub);
+ virtual ~NormalizingInputFilterMac();
+
+ // InputFilter overrides.
+ virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE;
+
+ private:
+ typedef std::map<int, protocol::KeyEvent> KeyPressedMap;
+
+ // Generate keyup events for any keys pressed with CMD.
+ void GenerateKeyupEvents();
+
+ // A map that stores pressed keycodes and the corresponding key event.
+ KeyPressedMap key_pressed_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterMac);
+};
+
+} // namespace remoting
diff --git a/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc b/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc
index 8221223f8d..2e645e5874 100644
--- a/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc
+++ b/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc
@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "remoting/client/plugin/normalizing_input_filter.h"
+#include "remoting/client/plugin/normalizing_input_filter_mac.h"
+
#include "remoting/proto/event.pb.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -46,8 +47,8 @@ KeyEvent MakeKeyEvent(uint32 keycode, bool pressed) {
// Test CapsLock press/release.
TEST(NormalizingInputFilterMacTest, CapsLock) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
@@ -64,8 +65,8 @@ TEST(NormalizingInputFilterMacTest, CapsLock) {
// Test without pressing command key.
TEST(NormalizingInputFilterMacTest, NoInjection) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
@@ -84,8 +85,8 @@ TEST(NormalizingInputFilterMacTest, NoInjection) {
// Test pressing command key and other normal keys.
TEST(NormalizingInputFilterMacTest, CmdKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
@@ -145,8 +146,8 @@ TEST(NormalizingInputFilterMacTest, CmdKey) {
// Test pressing command and special keys.
TEST(NormalizingInputFilterMacTest, SpecialKeys) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
@@ -188,8 +189,8 @@ TEST(NormalizingInputFilterMacTest, SpecialKeys) {
// Test pressing multiple command keys.
TEST(NormalizingInputFilterMacTest, MultipleCmdKeys) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
@@ -217,8 +218,8 @@ TEST(NormalizingInputFilterMacTest, MultipleCmdKeys) {
// Test press C key before command key.
TEST(NormalizingInputFilterMacTest, BeforeCmdKey) {
MockInputStub stub;
- scoped_ptr<protocol::InputFilter> processor =
- CreateNormalizingInputFilter(&stub);
+ scoped_ptr<protocol::InputFilter> processor(
+ new NormalizingInputFilterMac(&stub));
{
InSequence s;
diff --git a/remoting/client/plugin/pepper_input_handler.cc b/remoting/client/plugin/pepper_input_handler.cc
index 60a2746b8c..5bdebf39a4 100644
--- a/remoting/client/plugin/pepper_input_handler.cc
+++ b/remoting/client/plugin/pepper_input_handler.cc
@@ -17,13 +17,13 @@
namespace remoting {
PepperInputHandler::PepperInputHandler(
- pp::Instance* instance,
- protocol::InputStub* input_stub)
+ pp::Instance* instance)
: pp::MouseLock(instance),
instance_(instance),
- input_stub_(input_stub),
+ input_stub_(NULL),
callback_factory_(this),
has_focus_(false),
+ send_mouse_input_when_unfocused_(false),
mouse_lock_state_(MouseLockDisallowed),
wheel_delta_x_(0),
wheel_delta_y_(0),
@@ -31,8 +31,7 @@ PepperInputHandler::PepperInputHandler(
wheel_ticks_y_(0) {
}
-PepperInputHandler::~PepperInputHandler() {
-}
+PepperInputHandler::~PepperInputHandler() {}
// Helper function to get the USB key code using the Dev InputEvent interface.
uint32_t GetUsbKeyCode(pp::KeyboardInputEvent pp_key_event) {
@@ -69,13 +68,14 @@ bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
key_event.set_pressed(event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN);
key_event.set_lock_states(lock_states);
- input_stub_->InjectKeyEvent(key_event);
+ if (input_stub_)
+ input_stub_->InjectKeyEvent(key_event);
return true;
}
case PP_INPUTEVENT_TYPE_MOUSEDOWN:
case PP_INPUTEVENT_TYPE_MOUSEUP: {
- if (!has_focus_)
+ if (!has_focus_ && !send_mouse_input_when_unfocused_)
return false;
pp::MouseInputEvent pp_mouse_event(event);
@@ -106,7 +106,8 @@ bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
mouse_event.set_delta_y(delta.y());
}
- input_stub_->InjectMouseEvent(mouse_event);
+ if (input_stub_)
+ input_stub_->InjectMouseEvent(mouse_event);
}
return true;
}
@@ -114,7 +115,7 @@ bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
case PP_INPUTEVENT_TYPE_MOUSEMOVE:
case PP_INPUTEVENT_TYPE_MOUSEENTER:
case PP_INPUTEVENT_TYPE_MOUSELEAVE: {
- if (!has_focus_)
+ if (!has_focus_ && !send_mouse_input_when_unfocused_)
return false;
pp::MouseInputEvent pp_mouse_event(event);
@@ -129,12 +130,13 @@ bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
mouse_event.set_delta_y(delta.y());
}
- input_stub_->InjectMouseEvent(mouse_event);
+ if (input_stub_)
+ input_stub_->InjectMouseEvent(mouse_event);
return true;
}
case PP_INPUTEVENT_TYPE_WHEEL: {
- if (!has_focus_)
+ if (!has_focus_ && !send_mouse_input_when_unfocused_)
return false;
pp::WheelInputEvent pp_wheel_event(event);
@@ -175,7 +177,8 @@ bool PepperInputHandler::HandleInputEvent(const pp::InputEvent& event) {
mouse_event.set_wheel_ticks_x(ticks_x);
mouse_event.set_wheel_ticks_y(ticks_y);
- input_stub_->InjectMouseEvent(mouse_event);
+ if (input_stub_)
+ input_stub_->InjectMouseEvent(mouse_event);
}
return true;
}
diff --git a/remoting/client/plugin/pepper_input_handler.h b/remoting/client/plugin/pepper_input_handler.h
index c72243e5a0..9ec0475528 100644
--- a/remoting/client/plugin/pepper_input_handler.h
+++ b/remoting/client/plugin/pepper_input_handler.h
@@ -27,9 +27,13 @@ class InputStub;
class PepperInputHandler : public pp::MouseLock {
public:
// |instance| must outlive |this|.
- PepperInputHandler(pp::Instance* instance, protocol::InputStub* input_stub);
+ explicit PepperInputHandler(pp::Instance* instance);
virtual ~PepperInputHandler();
+ void set_input_stub(protocol::InputStub* input_stub) {
+ input_stub_ = input_stub;
+ }
+
bool HandleInputEvent(const pp::InputEvent& event);
// Enables locking the mouse when the host sets a completely transparent mouse
@@ -44,6 +48,12 @@ class PepperInputHandler : public pp::MouseLock {
void SetMouseCursor(scoped_ptr<pp::ImageData> image,
const pp::Point& hotspot);
+ // Enable or disable sending mouse input when the plugin does not have input
+ // focus.
+ void set_send_mouse_input_when_unfocused(bool send) {
+ send_mouse_input_when_unfocused_ = send;
+ }
+
private:
enum MouseLockState {
MouseLockDisallowed,
@@ -86,6 +96,10 @@ class PepperInputHandler : public pp::MouseLock {
// True if the plugin has focus.
bool has_focus_;
+ // True if the plugin should respond to mouse input even if it does not have
+ // keyboard focus.
+ bool send_mouse_input_when_unfocused_;
+
MouseLockState mouse_lock_state_;
// Accumulated sub-pixel and sub-tick deltas from wheel events.
diff --git a/remoting/client/plugin/pepper_module.cc b/remoting/client/plugin/pepper_module.cc
new file mode 100644
index 0000000000..b8e3bc5069
--- /dev/null
+++ b/remoting/client/plugin/pepper_module.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/at_exit.h"
+#include "base/compiler_specific.h"
+#include "ppapi/cpp/instance.h"
+#include "ppapi/cpp/module.h"
+#include "remoting/client/plugin/chromoting_instance.h"
+
+namespace remoting {
+
+class ChromotingModule : public pp::Module {
+ protected:
+ virtual pp::Instance* CreateInstance(PP_Instance instance) OVERRIDE {
+ pp::Instance* result = new ChromotingInstance(instance);
+ return result;
+ }
+ private:
+ base::AtExitManager at_exit_manager_;
+};
+
+} // namespace remoting
+
+namespace pp {
+
+// Factory function for your specialization of the Module object.
+Module* CreateModule() {
+ return new remoting::ChromotingModule();
+}
+
+} // namespace pp
diff --git a/remoting/client/server_log_entry.h b/remoting/client/server_log_entry.h
deleted file mode 100644
index 9f861d2538..0000000000
--- a/remoting/client/server_log_entry.h
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
-#define REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
-
-#include <map>
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/time/time.h"
-#include "remoting/protocol/connection_to_host.h"
-#include "remoting/protocol/errors.h"
-
-namespace buzz {
-class XmlElement;
-} // namespace buzz
-
-namespace remoting {
-
-class ChromotingStats;
-
-// Temporary namespace to prevent conflict with the same-named class in
-// remoting/host when linking unittests.
-//
-// TODO(lambroslambrou): Remove this and factor out any shared code.
-namespace client {
-
-class ServerLogEntry {
- public:
- // The mode of a connection.
- enum Mode {
- IT2ME,
- ME2ME
- };
-
- // Constructs a log stanza. The caller should add one or more log entry
- // stanzas as children of this stanza, before sending the log stanza to
- // the remoting bot.
- static scoped_ptr<buzz::XmlElement> MakeStanza();
-
- // Constructs a log entry for a session state change.
- static scoped_ptr<ServerLogEntry> MakeForSessionStateChange(
- remoting::protocol::ConnectionToHost::State state,
- remoting::protocol::ErrorCode error);
-
- // Constructs a log entry for reporting statistics.
- static scoped_ptr<ServerLogEntry> MakeForStatistics(
- remoting::ChromotingStats* statistics);
-
- // Constructs a log entry for reporting session ID is old.
- static scoped_ptr<ServerLogEntry> MakeForSessionIdOld(
- const std::string& session_id);
-
- // Constructs a log entry for reporting session ID is old.
- static scoped_ptr<ServerLogEntry> MakeForSessionIdNew(
- const std::string& session_id);
-
- ~ServerLogEntry();
-
- // Adds fields describing the client to this log entry.
- void AddClientFields();
-
- // Adds a field describing the mode of a connection to this log entry.
- void AddModeField(Mode mode);
-
- void AddEventName(const std::string& event_name);
- void AddSessionId(const std::string& session_id);
- void AddSessionDuration(base::TimeDelta duration);
-
- // Converts this object to an XML stanza.
- scoped_ptr<buzz::XmlElement> ToStanza() const;
-
- private:
- typedef std::map<std::string, std::string> ValuesMap;
-
- ServerLogEntry();
- void Set(const std::string& key, const std::string& value);
-
- static const char* GetValueSessionState(
- remoting::protocol::ConnectionToHost::State state);
- static const char* GetValueError(remoting::protocol::ErrorCode error);
- static const char* GetValueMode(Mode mode);
-
- ValuesMap values_map_;
-};
-
-} // namespace client
-
-} // namespace remoting
-
-#endif // REMOTING_CLIENT_SERVER_LOG_ENTRY_H_
diff --git a/remoting/client/server_log_entry.cc b/remoting/client/server_log_entry_client.cc
index cd580d8863..f664f8fc20 100644
--- a/remoting/client/server_log_entry.cc
+++ b/remoting/client/server_log_entry_client.cc
@@ -2,50 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "remoting/client/server_log_entry.h"
+#include "remoting/client/server_log_entry_client.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringize_macros.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
-#include "remoting/base/constants.h"
#include "remoting/client/chromoting_stats.h"
-#include "remoting/protocol/connection_to_host.h"
-#include "remoting/protocol/errors.h"
-#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+#include "remoting/jingle_glue/server_log_entry.h"
using base::StringPrintf;
using base::SysInfo;
-using buzz::QName;
-using buzz::XmlElement;
using remoting::protocol::ConnectionToHost;
+using remoting::protocol::ErrorCode;
namespace remoting {
-namespace client {
-
namespace {
-const char kLogCommand[] = "log";
-
-const char kLogEntry[] = "entry";
+const char kValueRoleClient[] = "client";
-const char kKeyEventName[] = "event-name";
const char kValueEventNameSessionState[] = "session-state";
const char kValueEventNameStatistics[] = "connection-statistics";
const char kValueEventNameSessionIdOld[] = "session-id-old";
const char kValueEventNameSessionIdNew[] = "session-id-new";
-const char kKeyRole[] = "role";
-const char kValueRoleClient[] = "client";
-
-const char kKeyMode[] = "mode";
-const char kValueModeIt2Me[] = "it2me";
-const char kValueModeMe2Me[] = "me2me";
+const char kKeySessionId[] = "session-id";
+const char kKeySessionDuration[] = "session-duration";
const char kKeySessionState[] = "session-state";
+const char kKeyConnectionError[] = "connection-error";
const char kValueSessionStateConnected[] = "connected";
const char kValueSessionStateClosed[] = "closed";
@@ -53,126 +38,7 @@ const char kKeyOsName[] = "os-name";
const char kKeyOsVersion[] = "os-version";
const char kKeyAppVersion[] = "app-version";
-const char kKeyCpu[] = "cpu";
-
-} // namespace
-
-ServerLogEntry::ServerLogEntry() {
-}
-
-ServerLogEntry::~ServerLogEntry() {
-}
-
-// static
-scoped_ptr<buzz::XmlElement> ServerLogEntry::MakeStanza() {
- return scoped_ptr<buzz::XmlElement>(
- new XmlElement(QName(kChromotingXmlNamespace, kLogCommand)));
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionStateChange(
- protocol::ConnectionToHost::State state,
- protocol::ErrorCode error) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleClient);
- entry->Set(kKeyEventName, kValueEventNameSessionState);
-
- entry->Set(kKeySessionState, GetValueSessionState(state));
- if (error != protocol::OK) {
- entry->Set("connection-error", GetValueError(error));
- }
-
- return entry.Pass();
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForStatistics(
- ChromotingStats* statistics) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleClient);
- entry->Set(kKeyEventName, kValueEventNameStatistics);
-
- entry->Set("video-bandwidth",
- StringPrintf("%.2f", statistics->video_bandwidth()->Rate()));
- entry->Set("capture-latency",
- StringPrintf("%.2f", statistics->video_capture_ms()->Average()));
- entry->Set("encode-latency",
- StringPrintf("%.2f", statistics->video_encode_ms()->Average()));
- entry->Set("decode-latency",
- StringPrintf("%.2f", statistics->video_decode_ms()->Average()));
- entry->Set("render-latency",
- StringPrintf("%.2f", statistics->video_frame_rate()->Rate()));
- entry->Set("roundtrip-latency",
- StringPrintf("%.2f", statistics->round_trip_ms()->Average()));
-
- return entry.Pass();
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionIdOld(
- const std::string& session_id) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleClient);
- entry->Set(kKeyEventName, kValueEventNameSessionIdOld);
- entry->AddSessionId(session_id);
- return entry.Pass();
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionIdNew(
- const std::string& session_id) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleClient);
- entry->Set(kKeyEventName, kValueEventNameSessionIdNew);
- entry->AddSessionId(session_id);
- return entry.Pass();
-}
-
-void ServerLogEntry::AddClientFields() {
- Set(kKeyOsName, SysInfo::OperatingSystemName());
- Set(kKeyOsVersion, SysInfo::OperatingSystemVersion());
- Set(kKeyAppVersion, STRINGIZE(VERSION));
- Set(kKeyCpu, SysInfo::OperatingSystemArchitecture());
-};
-
-void ServerLogEntry::AddModeField(ServerLogEntry::Mode mode) {
- Set(kKeyMode, GetValueMode(mode));
-}
-
-void ServerLogEntry::AddSessionId(const std::string& session_id) {
- Set("session-id", session_id);
-}
-
-void ServerLogEntry::AddSessionDuration(base::TimeDelta duration) {
- Set("session-duration", base::Int64ToString(duration.InSeconds()));
-}
-
-// static
-const char* ServerLogEntry::GetValueMode(ServerLogEntry::Mode mode) {
- switch (mode) {
- case IT2ME:
- return kValueModeIt2Me;
- case ME2ME:
- return kValueModeMe2Me;
- default:
- NOTREACHED();
- return NULL;
- }
-}
-
-scoped_ptr<XmlElement> ServerLogEntry::ToStanza() const {
- scoped_ptr<XmlElement> stanza(new XmlElement(QName(
- kChromotingXmlNamespace, kLogEntry)));
- ValuesMap::const_iterator iter;
- for (iter = values_map_.begin(); iter != values_map_.end(); ++iter) {
- stanza->AddAttr(QName(std::string(), iter->first), iter->second);
- }
- return stanza.Pass();
-}
-
-// static
-const char* ServerLogEntry::GetValueSessionState(
- ConnectionToHost::State state) {
+const char* GetValueSessionState(ConnectionToHost::State state) {
switch (state) {
// Where possible, these are the same strings that the webapp sends for the
// corresponding state - see remoting/webapp/server_log_entry.js.
@@ -194,8 +60,7 @@ const char* ServerLogEntry::GetValueSessionState(
}
}
-// static
-const char* ServerLogEntry::GetValueError(protocol::ErrorCode error) {
+const char* GetValueError(ErrorCode error) {
switch (error) {
// Where possible, these are the same strings that the webapp sends for the
// corresponding error - see remoting/webapp/server_log_entry.js.
@@ -225,14 +90,77 @@ const char* ServerLogEntry::GetValueError(protocol::ErrorCode error) {
}
}
-void ServerLogEntry::AddEventName(const std::string& event_name) {
- Set("event-name", event_name);
+} // namespace
+
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionStateChange(
+ ConnectionToHost::State state,
+ ErrorCode error) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleClient);
+ entry->AddEventNameField(kValueEventNameSessionState);
+
+ entry->Set(kKeySessionState, GetValueSessionState(state));
+ if (error != protocol::OK) {
+ entry->Set(kKeyConnectionError, GetValueError(error));
+ }
+
+ return entry.Pass();
+}
+
+scoped_ptr<ServerLogEntry> MakeLogEntryForStatistics(
+ ChromotingStats* statistics) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleClient);
+ entry->AddEventNameField(kValueEventNameStatistics);
+
+ entry->Set("video-bandwidth",
+ StringPrintf("%.2f", statistics->video_bandwidth()->Rate()));
+ entry->Set("capture-latency",
+ StringPrintf("%.2f", statistics->video_capture_ms()->Average()));
+ entry->Set("encode-latency",
+ StringPrintf("%.2f", statistics->video_encode_ms()->Average()));
+ entry->Set("decode-latency",
+ StringPrintf("%.2f", statistics->video_decode_ms()->Average()));
+ entry->Set("render-latency",
+ StringPrintf("%.2f", statistics->video_frame_rate()->Rate()));
+ entry->Set("roundtrip-latency",
+ StringPrintf("%.2f", statistics->round_trip_ms()->Average()));
+
+ return entry.Pass();
+}
+
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionIdOld(
+ const std::string& session_id) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleClient);
+ entry->AddEventNameField(kValueEventNameSessionIdOld);
+ AddSessionIdToLogEntry(entry.get(), session_id);
+ return entry.Pass();
}
-void ServerLogEntry::Set(const std::string& key, const std::string& value) {
- values_map_[key] = value;
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionIdNew(
+ const std::string& session_id) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleClient);
+ entry->AddEventNameField(kValueEventNameSessionIdNew);
+ AddSessionIdToLogEntry(entry.get(), session_id);
+ return entry.Pass();
+}
+
+void AddClientFieldsToLogEntry(ServerLogEntry* entry) {
+ entry->Set(kKeyOsName, SysInfo::OperatingSystemName());
+ entry->Set(kKeyOsVersion, SysInfo::OperatingSystemVersion());
+ entry->Set(kKeyAppVersion, STRINGIZE(VERSION));
+ entry->AddCpuField();
}
-} // namespace client
+void AddSessionIdToLogEntry(ServerLogEntry* entry, const std::string& id) {
+ entry->Set(kKeySessionId, id);
+}
+
+void AddSessionDurationToLogEntry(ServerLogEntry* entry,
+ base::TimeDelta duration) {
+ entry->Set(kKeySessionDuration, base::Int64ToString(duration.InSeconds()));
+}
} // namespace remoting
diff --git a/remoting/client/server_log_entry_client.h b/remoting/client/server_log_entry_client.h
new file mode 100644
index 0000000000..b2ef262897
--- /dev/null
+++ b/remoting/client/server_log_entry_client.h
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_CLIENT_SERVER_LOG_ENTRY_CLIENT_H_
+#define REMOTING_CLIENT_SERVER_LOG_ENTRY_CLIENT_H_
+
+#include "base/time/time.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/errors.h"
+
+namespace remoting {
+
+class ChromotingStats;
+class ServerLogEntry;
+
+// Constructs a log entry for a session state change.
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionStateChange(
+ protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error);
+
+// Constructs a log entry for reporting statistics.
+scoped_ptr<ServerLogEntry> MakeLogEntryForStatistics(
+ ChromotingStats* statistics);
+
+// Constructs a log entry for reporting session ID is old.
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionIdOld(
+ const std::string& session_id);
+
+// Constructs a log entry for reporting session ID is old.
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionIdNew(
+ const std::string& session_id);
+
+void AddClientFieldsToLogEntry(ServerLogEntry* entry);
+void AddSessionIdToLogEntry(ServerLogEntry* entry, const std::string& id);
+void AddSessionDurationToLogEntry(ServerLogEntry* entry,
+ base::TimeDelta duration);
+
+} // namespace remoting
+
+#endif // REMOTING_CLIENT_SERVER_LOG_ENTRY_CLIENT_H_
diff --git a/remoting/client/server_log_entry_client_unittest.cc b/remoting/client/server_log_entry_client_unittest.cc
new file mode 100644
index 0000000000..9861b18096
--- /dev/null
+++ b/remoting/client/server_log_entry_client_unittest.cc
@@ -0,0 +1,125 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringize_macros.h"
+#include "base/sys_info.h"
+#include "remoting/client/chromoting_stats.h"
+#include "remoting/client/server_log_entry_client.h"
+#include "remoting/jingle_glue/server_log_entry.h"
+#include "remoting/jingle_glue/server_log_entry_unittest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+
+using base::SysInfo;
+using buzz::XmlAttr;
+using buzz::XmlElement;
+using remoting::protocol::ConnectionToHost;
+
+namespace remoting {
+
+TEST(ServerLogEntryClientTest, SessionStateChange) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(
+ ConnectionToHost::CONNECTED, remoting::protocol::OK));
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "session-state";
+ key_value_pairs["session-state"] = "connected";
+ std::set<std::string> keys;
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+}
+
+TEST(ServerLogEntryClientTest, SessionStateChangeWithError) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(
+ ConnectionToHost::FAILED, remoting::protocol::PEER_IS_OFFLINE));
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "session-state";
+ key_value_pairs["session-state"] = "connection-failed";
+ key_value_pairs["connection-error"] = "host-is-offline";
+ std::set<std::string> keys;
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+}
+
+TEST(ServerLogEntryClientTest, Statistics) {
+ ChromotingStats statistics;
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForStatistics(&statistics));
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "connection-statistics";
+ std::set<std::string> keys;
+ keys.insert("video-bandwidth");
+ keys.insert("capture-latency");
+ keys.insert("encode-latency");
+ keys.insert("decode-latency");
+ keys.insert("render-latency");
+ keys.insert("roundtrip-latency");
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+}
+
+TEST(ServerLogEntryClientTest, SessionIdChanged) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionIdOld("abc"));
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "session-id-old";
+ key_value_pairs["session-id"] = "abc";
+ std::set<std::string> keys;
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+
+ entry = MakeLogEntryForSessionIdNew("def");
+ stanza = entry->ToStanza();
+ key_value_pairs["event-name"] = "session-id-new";
+ key_value_pairs["session-id"] = "def";
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+}
+
+TEST(ServerLogEntryClientTest, AddClientFields) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(
+ ConnectionToHost::CONNECTED, remoting::protocol::OK));
+ AddClientFieldsToLogEntry(entry.get());
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "session-state";
+ key_value_pairs["session-state"] = "connected";
+ key_value_pairs["os-name"] = SysInfo::OperatingSystemName();
+ key_value_pairs["os-version"] = SysInfo::OperatingSystemVersion();
+ key_value_pairs["app-version"] = STRINGIZE(VERSION);
+ key_value_pairs["cpu"] = SysInfo::OperatingSystemArchitecture();
+ std::set<std::string> keys;
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error)) <<
+ error;
+}
+
+TEST(ServerLogEntryClientTest, AddSessionDuration) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(
+ ConnectionToHost::CONNECTED, remoting::protocol::OK));
+ AddSessionDurationToLogEntry(entry.get(), base::TimeDelta::FromSeconds(123));
+ scoped_ptr<XmlElement> stanza = entry->ToStanza();
+ std::string error;
+ std::map<std::string, std::string> key_value_pairs;
+ key_value_pairs["role"] = "client";
+ key_value_pairs["event-name"] = "session-state";
+ key_value_pairs["session-state"] = "connected";
+ key_value_pairs["session-duration"] = "123";
+ std::set<std::string> keys;
+ ASSERT_TRUE(VerifyStanza(key_value_pairs, keys, stanza.get(), &error))
+ << error;
+}
+
+} // namespace remoting
diff --git a/remoting/codec/video_decoder_vpx.cc b/remoting/codec/video_decoder_vpx.cc
index e414ee8e5e..0be92018a6 100644
--- a/remoting/codec/video_decoder_vpx.cc
+++ b/remoting/codec/video_decoder_vpx.cc
@@ -12,6 +12,7 @@
#include "media/base/media.h"
#include "media/base/yuv_convert.h"
#include "remoting/base/util.h"
+#include "third_party/libyuv/include/libyuv/convert_argb.h"
extern "C" {
#define VPX_CODEC_DISABLE_COMPAT 1
@@ -177,84 +178,128 @@ void VideoDecoderVpx::RenderFrame(const webrtc::DesktopSize& view_size,
webrtc::DesktopRect source_clip =
webrtc::DesktopRect::MakeWH(last_image_->d_w, last_image_->d_h);
- // ScaleYUVToRGB32WithRect does not currently support up-scaling. We won't
- // be asked to up-scale except during resizes or if page zoom is >100%, so
- // we work-around the limitation by using the slower ScaleYUVToRGB32.
- // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can up-scale.
- if (!updated_region_.is_empty() &&
- (source_clip.width() < view_size.width() ||
- source_clip.height() < view_size.height())) {
- // We're scaling only |clip_area| into the |image_buffer|, so we need to
- // work out which source rectangle that corresponds to.
- webrtc::DesktopRect source_rect =
- ScaleRect(clip_area, view_size, screen_size_);
- source_rect = webrtc::DesktopRect::MakeLTRB(
- RoundToTwosMultiple(source_rect.left()),
- RoundToTwosMultiple(source_rect.top()),
- source_rect.right(),
- source_rect.bottom());
-
- // If there were no changes within the clip source area then don't render.
- webrtc::DesktopRegion intersection(source_rect);
- intersection.IntersectWith(updated_region_);
- if (intersection.is_empty())
+ // VP8 only outputs I420 frames, but VP9 can also produce I444.
+ switch (last_image_->fmt) {
+ case VPX_IMG_FMT_I444: {
+ // TODO(wez): Add scaling support to the I444 conversion path.
+ if (view_size.equals(screen_size_)) {
+ for (webrtc::DesktopRegion::Iterator i(updated_region_);
+ !i.IsAtEnd(); i.Advance()) {
+ // Determine the scaled area affected by this rectangle changing.
+ webrtc::DesktopRect rect = i.rect();
+ rect.IntersectWith(source_clip);
+ rect.IntersectWith(clip_area);
+ if (rect.is_empty())
+ continue;
+
+ int image_offset = image_stride * rect.top() +
+ rect.left() * VideoDecoder::kBytesPerPixel;
+ int y_offset = last_image_->stride[0] * rect.top() + rect.left();
+ int u_offset = last_image_->stride[1] * rect.top() + rect.left();
+ int v_offset = last_image_->stride[2] * rect.top() + rect.left();
+ libyuv::I444ToARGB(last_image_->planes[0] + y_offset,
+ last_image_->stride[0],
+ last_image_->planes[1] + u_offset,
+ last_image_->stride[1],
+ last_image_->planes[2] + v_offset,
+ last_image_->stride[2],
+ image_buffer + image_offset, image_stride,
+ rect.width(), rect.height());
+
+ output_region->AddRect(rect);
+ }
+ }
+ break;
+ }
+ case VPX_IMG_FMT_I420: {
+ // ScaleYUVToRGB32WithRect does not currently support up-scaling. We
+ // won't be asked to up-scale except during resizes or if page zoom is
+ // >100%, so we work-around the limitation by using the slower
+ // ScaleYUVToRGB32.
+ // TODO(wez): Remove this hack if/when ScaleYUVToRGB32WithRect can
+ // up-scale.
+ if (!updated_region_.is_empty() &&
+ (source_clip.width() < view_size.width() ||
+ source_clip.height() < view_size.height())) {
+ // We're scaling only |clip_area| into the |image_buffer|, so we need to
+ // work out which source rectangle that corresponds to.
+ webrtc::DesktopRect source_rect =
+ ScaleRect(clip_area, view_size, screen_size_);
+ source_rect = webrtc::DesktopRect::MakeLTRB(
+ RoundToTwosMultiple(source_rect.left()),
+ RoundToTwosMultiple(source_rect.top()),
+ source_rect.right(),
+ source_rect.bottom());
+
+ // If there were no changes within the clip source area then don't
+ // render.
+ webrtc::DesktopRegion intersection(source_rect);
+ intersection.IntersectWith(updated_region_);
+ if (intersection.is_empty())
+ return;
+
+ // Scale & convert the entire clip area.
+ int y_offset = CalculateYOffset(source_rect.left(), source_rect.top(),
+ last_image_->stride[0]);
+ int uv_offset = CalculateUVOffset(source_rect.left(), source_rect.top(),
+ last_image_->stride[1]);
+ ScaleYUVToRGB32(last_image_->planes[0] + y_offset,
+ last_image_->planes[1] + uv_offset,
+ last_image_->planes[2] + uv_offset,
+ image_buffer,
+ source_rect.width(),
+ source_rect.height(),
+ clip_area.width(),
+ clip_area.height(),
+ last_image_->stride[0],
+ last_image_->stride[1],
+ image_stride,
+ media::YV12,
+ media::ROTATE_0,
+ media::FILTER_BILINEAR);
+
+ output_region->AddRect(clip_area);
+ updated_region_.Subtract(source_rect);
+ return;
+ }
+
+ for (webrtc::DesktopRegion::Iterator i(updated_region_);
+ !i.IsAtEnd(); i.Advance()) {
+ // Determine the scaled area affected by this rectangle changing.
+ webrtc::DesktopRect rect = i.rect();
+ rect.IntersectWith(source_clip);
+ if (rect.is_empty())
+ continue;
+ rect = ScaleRect(rect, screen_size_, view_size);
+ rect.IntersectWith(clip_area);
+ if (rect.is_empty())
+ continue;
+
+ ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0],
+ last_image_->planes[1],
+ last_image_->planes[2],
+ last_image_->stride[0],
+ last_image_->stride[1],
+ screen_size_,
+ source_clip,
+ image_buffer,
+ image_stride,
+ view_size,
+ clip_area,
+ rect);
+
+ output_region->AddRect(rect);
+ }
+
+ updated_region_.Subtract(ScaleRect(clip_area, view_size, screen_size_));
+ break;
+ }
+ default: {
+ LOG(ERROR) << "Unsupported image format:" << last_image_->fmt;
return;
-
- // Scale & convert the entire clip area.
- int y_offset = CalculateYOffset(source_rect.left(), source_rect.top(),
- last_image_->stride[0]);
- int uv_offset = CalculateUVOffset(source_rect.left(), source_rect.top(),
- last_image_->stride[1]);
- ScaleYUVToRGB32(last_image_->planes[0] + y_offset,
- last_image_->planes[1] + uv_offset,
- last_image_->planes[2] + uv_offset,
- image_buffer,
- source_rect.width(),
- source_rect.height(),
- clip_area.width(),
- clip_area.height(),
- last_image_->stride[0],
- last_image_->stride[1],
- image_stride,
- media::YV12,
- media::ROTATE_0,
- media::FILTER_BILINEAR);
-
- output_region->AddRect(clip_area);
- updated_region_.Subtract(source_rect);
- return;
- }
-
- for (webrtc::DesktopRegion::Iterator i(updated_region_);
- !i.IsAtEnd(); i.Advance()) {
- // Determine the scaled area affected by this rectangle changing.
- webrtc::DesktopRect rect = i.rect();
- rect.IntersectWith(source_clip);
- if (rect.is_empty())
- continue;
- rect = ScaleRect(rect, screen_size_, view_size);
- rect.IntersectWith(clip_area);
- if (rect.is_empty())
- continue;
-
- ConvertAndScaleYUVToRGB32Rect(last_image_->planes[0],
- last_image_->planes[1],
- last_image_->planes[2],
- last_image_->stride[0],
- last_image_->stride[1],
- screen_size_,
- source_clip,
- image_buffer,
- image_stride,
- view_size,
- clip_area,
- rect);
-
- output_region->AddRect(rect);
+ }
}
- updated_region_.Subtract(ScaleRect(clip_area, view_size, screen_size_));
-
for (webrtc::DesktopRegion::Iterator i(transparent_region_);
!i.IsAtEnd(); i.Advance()) {
// Determine the scaled area affected by this rectangle changing.
diff --git a/remoting/codec/video_encoder.h b/remoting/codec/video_encoder.h
index f0f8a2fc27..f497eb3b4c 100644
--- a/remoting/codec/video_encoder.h
+++ b/remoting/codec/video_encoder.h
@@ -21,6 +21,10 @@ class VideoEncoder {
public:
virtual ~VideoEncoder() {}
+ // Request that the encoder provide lossless encoding, or color, if possible.
+ virtual void SetLosslessEncode(bool want_lossless) {}
+ virtual void SetLosslessColor(bool want_lossless) {}
+
// Encode an image stored in |frame|.
virtual scoped_ptr<VideoPacket> Encode(const webrtc::DesktopFrame& frame) = 0;
};
diff --git a/remoting/codec/video_encoder_vpx.cc b/remoting/codec/video_encoder_vpx.cc
index 59bb5dc0b2..fba110abf0 100644
--- a/remoting/codec/video_encoder_vpx.cc
+++ b/remoting/codec/video_encoder_vpx.cc
@@ -5,11 +5,12 @@
#include "remoting/codec/video_encoder_vpx.h"
#include "base/bind.h"
+#include "base/command_line.h"
#include "base/logging.h"
#include "base/sys_info.h"
-#include "media/base/yuv_convert.h"
#include "remoting/base/util.h"
#include "remoting/proto/video.pb.h"
+#include "third_party/libyuv/include/libyuv/convert_from_argb.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
@@ -24,10 +25,20 @@ namespace remoting {
namespace {
+// Name of command-line flag to enable VP9 to use I444 by default.
+const char kEnableI444SwitchName[] = "enable-i444";
+
+// Number of bytes in an RGBx pixel.
+const int kBytesPerRgbPixel = 4;
+
// Defines the dimension of a macro block. This is used to compute the active
// map for the encoder.
const int kMacroBlockSize = 16;
+// Magic encoder profile numbers for I420 and I444 input formats.
+const int kVp9I420ProfileNumber = 0;
+const int kVp9I444ProfileNumber = 1;
+
void SetCommonCodecParameters(const webrtc::DesktopSize& size,
vpx_codec_enc_cfg_t* config) {
// Use millisecond granularity time base.
@@ -90,7 +101,9 @@ ScopedVpxCodec CreateVP8Codec(const webrtc::DesktopSize& size) {
return codec.Pass();
}
-ScopedVpxCodec CreateVP9Codec(const webrtc::DesktopSize& size) {
+ScopedVpxCodec CreateVP9Codec(const webrtc::DesktopSize& size,
+ bool lossless_color,
+ bool lossless_encode) {
ScopedVpxCodec codec(new vpx_codec_ctx_t);
// Configure the encoder.
@@ -103,12 +116,19 @@ ScopedVpxCodec CreateVP9Codec(const webrtc::DesktopSize& size) {
SetCommonCodecParameters(size, &config);
- // Configure VP9 for I420 source frames.
- config.g_profile = 0;
-
- // Disable quantization entirely, putting the encoder in "lossless" mode.
- config.rc_min_quantizer = 0;
- config.rc_max_quantizer = 0;
+ // Configure VP9 for I420 or I444 source frames.
+ config.g_profile =
+ lossless_color ? kVp9I444ProfileNumber : kVp9I420ProfileNumber;
+
+ if (lossless_encode) {
+ // Disable quantization entirely, putting the encoder in "lossless" mode.
+ config.rc_min_quantizer = 0;
+ config.rc_max_quantizer = 0;
+ } else {
+ // Lossy encode using the same settings as for VP8.
+ config.rc_min_quantizer = 20;
+ config.rc_max_quantizer = 30;
+ }
if (vpx_codec_enc_init(codec.get(), algo, &config, 0))
return ScopedVpxCodec();
@@ -128,22 +148,97 @@ ScopedVpxCodec CreateVP9Codec(const webrtc::DesktopSize& size) {
return codec.Pass();
}
-} // namespace
+void CreateImage(bool use_i444,
+ const webrtc::DesktopSize& size,
+ scoped_ptr<vpx_image_t>* out_image,
+ scoped_ptr<uint8[]>* out_image_buffer) {
+ DCHECK(!size.is_empty());
+
+ scoped_ptr<vpx_image_t> image(new vpx_image_t());
+ memset(image.get(), 0, sizeof(vpx_image_t));
+
+ // libvpx seems to require both to be assigned.
+ image->d_w = size.width();
+ image->w = size.width();
+ image->d_h = size.height();
+ image->h = size.height();
+
+ // libvpx should derive chroma shifts from|fmt| but currently has a bug:
+ // https://code.google.com/p/webm/issues/detail?id=627
+ if (use_i444) {
+ image->fmt = VPX_IMG_FMT_I444;
+ image->x_chroma_shift = 0;
+ image->y_chroma_shift = 0;
+ } else { // I420
+ image->fmt = VPX_IMG_FMT_YV12;
+ image->x_chroma_shift = 1;
+ image->y_chroma_shift = 1;
+ }
+
+ // libyuv's fast-path requires 16-byte aligned pointers and strides, so pad
+ // the Y, U and V planes' strides to multiples of 16 bytes.
+ const int y_stride = ((image->w - 1) & ~15) + 16;
+ const int uv_unaligned_stride = y_stride >> image->x_chroma_shift;
+ const int uv_stride = ((uv_unaligned_stride - 1) & ~15) + 16;
+
+ // libvpx accesses the source image in macro blocks, and will over-read
+ // if the image is not padded out to the next macroblock: crbug.com/119633.
+ // Pad the Y, U and V planes' height out to compensate.
+ // Assuming macroblocks are 16x16, aligning the planes' strides above also
+ // macroblock aligned them.
+ DCHECK_EQ(16, kMacroBlockSize);
+ const int y_rows = ((image->h - 1) & ~(kMacroBlockSize-1)) + kMacroBlockSize;
+ const int uv_rows = y_rows >> image->y_chroma_shift;
+
+ // Allocate a YUV buffer large enough for the aligned data & padding.
+ const int buffer_size = y_stride * y_rows + 2*uv_stride * uv_rows;
+ scoped_ptr<uint8[]> image_buffer(new uint8[buffer_size]);
+
+ // Reset image value to 128 so we just need to fill in the y plane.
+ memset(image_buffer.get(), 128, buffer_size);
+
+ // Fill in the information for |image_|.
+ unsigned char* uchar_buffer =
+ reinterpret_cast<unsigned char*>(image_buffer.get());
+ image->planes[0] = uchar_buffer;
+ image->planes[1] = image->planes[0] + y_stride * y_rows;
+ image->planes[2] = image->planes[1] + uv_stride * uv_rows;
+ image->stride[0] = y_stride;
+ image->stride[1] = uv_stride;
+ image->stride[2] = uv_stride;
+
+ *out_image = image.Pass();
+ *out_image_buffer = image_buffer.Pass();
+}
+
+} // namespace
// static
scoped_ptr<VideoEncoderVpx> VideoEncoderVpx::CreateForVP8() {
- return scoped_ptr<VideoEncoderVpx>(
- new VideoEncoderVpx(base::Bind(&CreateVP8Codec)));
+ return scoped_ptr<VideoEncoderVpx>(new VideoEncoderVpx(false));
}
// static
scoped_ptr<VideoEncoderVpx> VideoEncoderVpx::CreateForVP9() {
- return scoped_ptr<VideoEncoderVpx>(
- new VideoEncoderVpx(base::Bind(&CreateVP9Codec)));
+ return scoped_ptr<VideoEncoderVpx>(new VideoEncoderVpx(true));
}
VideoEncoderVpx::~VideoEncoderVpx() {}
+void VideoEncoderVpx::SetLosslessEncode(bool want_lossless) {
+ if (use_vp9_ && (want_lossless != lossless_encode_)) {
+ lossless_encode_ = want_lossless;
+ codec_.reset(); // Force encoder re-initialization.
+ }
+}
+
+void VideoEncoderVpx::SetLosslessColor(bool want_lossless) {
+ if (use_vp9_ && (want_lossless != lossless_color_)) {
+ lossless_color_ = want_lossless;
+ codec_.reset(); // Force encoder re-initialization.
+ }
+}
+
scoped_ptr<VideoPacket> VideoEncoderVpx::Encode(
const webrtc::DesktopFrame& frame) {
DCHECK_LE(32, frame.size().width());
@@ -233,69 +328,43 @@ scoped_ptr<VideoPacket> VideoEncoderVpx::Encode(
return packet.Pass();
}
-VideoEncoderVpx::VideoEncoderVpx(const InitializeCodecCallback& init_codec)
- : init_codec_(init_codec),
+VideoEncoderVpx::VideoEncoderVpx(bool use_vp9)
+ : use_vp9_(use_vp9),
+ lossless_encode_(false),
+ lossless_color_(false),
active_map_width_(0),
active_map_height_(0) {
+ if (use_vp9_) {
+ // Use lossless encoding mode by default.
+ SetLosslessEncode(true);
+
+ // Use I444 colour space, by default, if specified on the command-line.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableI444SwitchName)) {
+ SetLosslessColor(true);
+ }
+ }
}
bool VideoEncoderVpx::Initialize(const webrtc::DesktopSize& size) {
- codec_.reset();
-
- image_.reset(new vpx_image_t());
- memset(image_.get(), 0, sizeof(vpx_image_t));
-
- image_->fmt = VPX_IMG_FMT_YV12;
+ DCHECK(use_vp9_ || !lossless_color_);
+ DCHECK(use_vp9_ || !lossless_encode_);
- // libvpx seems to require both to be assigned.
- image_->d_w = size.width();
- image_->w = size.width();
- image_->d_h = size.height();
- image_->h = size.height();
+ codec_.reset();
- // libvpx should derive this from|fmt| but currently has a bug:
- // https://code.google.com/p/webm/issues/detail?id=627
- image_->x_chroma_shift = 1;
- image_->y_chroma_shift = 1;
+ // (Re)Create the VPX image structure and pixel buffer.
+ CreateImage(lossless_color_, size, &image_, &image_buffer_);
// Initialize active map.
active_map_width_ = (image_->w + kMacroBlockSize - 1) / kMacroBlockSize;
active_map_height_ = (image_->h + kMacroBlockSize - 1) / kMacroBlockSize;
active_map_.reset(new uint8[active_map_width_ * active_map_height_]);
- // libyuv's fast-path requires 16-byte aligned pointers and strides, so pad
- // the Y, U and V planes' strides to multiples of 16 bytes.
- const int y_stride = ((image_->w - 1) & ~15) + 16;
- const int uv_unaligned_stride = y_stride / 2;
- const int uv_stride = ((uv_unaligned_stride - 1) & ~15) + 16;
-
- // libvpx accesses the source image in macro blocks, and will over-read
- // if the image is not padded out to the next macroblock: crbug.com/119633.
- // Pad the Y, U and V planes' height out to compensate.
- // Assuming macroblocks are 16x16, aligning the planes' strides above also
- // macroblock aligned them.
- DCHECK_EQ(16, kMacroBlockSize);
- const int y_rows = active_map_height_ * kMacroBlockSize;
- const int uv_rows = y_rows / 2;
-
- // Allocate a YUV buffer large enough for the aligned data & padding.
- const int buffer_size = y_stride * y_rows + 2 * uv_stride * uv_rows;
- yuv_image_.reset(new uint8[buffer_size]);
-
- // Reset image value to 128 so we just need to fill in the y plane.
- memset(yuv_image_.get(), 128, buffer_size);
-
- // Fill in the information for |image_|.
- unsigned char* image = reinterpret_cast<unsigned char*>(yuv_image_.get());
- image_->planes[0] = image;
- image_->planes[1] = image_->planes[0] + y_stride * y_rows;
- image_->planes[2] = image_->planes[1] + uv_stride * uv_rows;
- image_->stride[0] = y_stride;
- image_->stride[1] = uv_stride;
- image_->stride[2] = uv_stride;
-
- // Initialize the codec.
- codec_ = init_codec_.Run(size);
+ // (Re)Initialize the codec.
+ if (use_vp9_) {
+ codec_ = CreateVP9Codec(size, lossless_color_, lossless_encode_);
+ } else {
+ codec_ = CreateVP8Codec(size);
+ }
return codec_;
}
@@ -336,13 +405,40 @@ void VideoEncoderVpx::PrepareImage(const webrtc::DesktopFrame& frame,
uint8* y_data = image_->planes[0];
uint8* u_data = image_->planes[1];
uint8* v_data = image_->planes[2];
- for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd();
- r.Advance()) {
- const webrtc::DesktopRect& rect = r.rect();
- ConvertRGB32ToYUVWithRect(
- rgb_data, y_data, u_data, v_data,
- rect.left(), rect.top(), rect.width(), rect.height(),
- rgb_stride, y_stride, uv_stride);
+
+ switch (image_->fmt) {
+ case VPX_IMG_FMT_I444:
+ for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd();
+ r.Advance()) {
+ const webrtc::DesktopRect& rect = r.rect();
+ int rgb_offset = rgb_stride * rect.top() +
+ rect.left() * kBytesPerRgbPixel;
+ int yuv_offset = uv_stride * rect.top() + rect.left();
+ libyuv::ARGBToI444(rgb_data + rgb_offset, rgb_stride,
+ y_data + yuv_offset, y_stride,
+ u_data + yuv_offset, uv_stride,
+ v_data + yuv_offset, uv_stride,
+ rect.width(), rect.height());
+ }
+ break;
+ case VPX_IMG_FMT_YV12:
+ for (webrtc::DesktopRegion::Iterator r(*updated_region); !r.IsAtEnd();
+ r.Advance()) {
+ const webrtc::DesktopRect& rect = r.rect();
+ int rgb_offset = rgb_stride * rect.top() +
+ rect.left() * kBytesPerRgbPixel;
+ int y_offset = y_stride * rect.top() + rect.left();
+ int uv_offset = uv_stride * rect.top() / 2 + rect.left() / 2;
+ libyuv::ARGBToI420(rgb_data + rgb_offset, rgb_stride,
+ y_data + y_offset, y_stride,
+ u_data + uv_offset, uv_stride,
+ v_data + uv_offset, uv_stride,
+ rect.width(), rect.height());
+ }
+ break;
+ default:
+ NOTREACHED();
+ break;
}
}
diff --git a/remoting/codec/video_encoder_vpx.h b/remoting/codec/video_encoder_vpx.h
index dd7fb2ee51..133b754ec4 100644
--- a/remoting/codec/video_encoder_vpx.h
+++ b/remoting/codec/video_encoder_vpx.h
@@ -28,14 +28,13 @@ class VideoEncoderVpx : public VideoEncoder {
virtual ~VideoEncoderVpx();
// VideoEncoder interface.
+ virtual void SetLosslessEncode(bool want_lossless) OVERRIDE;
+ virtual void SetLosslessColor(bool want_lossless) OVERRIDE;
virtual scoped_ptr<VideoPacket> Encode(
const webrtc::DesktopFrame& frame) OVERRIDE;
private:
- typedef base::Callback<ScopedVpxCodec(const webrtc::DesktopSize&)>
- InitializeCodecCallback;
-
- VideoEncoderVpx(const InitializeCodecCallback& init_codec);
+ explicit VideoEncoderVpx(bool use_vp9);
// Initializes the codec for frames of |size|. Returns true if successful.
bool Initialize(const webrtc::DesktopSize& size);
@@ -49,17 +48,25 @@ class VideoEncoderVpx : public VideoEncoder {
// given to the encoder to speed up encoding.
void PrepareActiveMap(const webrtc::DesktopRegion& updated_region);
- InitializeCodecCallback init_codec_;
+ // True if the encoder should generate VP9, false for VP8.
+ bool use_vp9_;
+
+ // Options controlling VP9 encode quantization and color space.
+ // These are always off (false) for VP8.
+ bool lossless_encode_;
+ bool lossless_color_;
ScopedVpxCodec codec_;
+ base::TimeTicks timestamp_base_;
+
+ // VPX image and buffer to hold the actual YUV planes.
scoped_ptr<vpx_image_t> image_;
+ scoped_ptr<uint8[]> image_buffer_;
+
+ // Active map used to optimize out processing of un-changed macroblocks.
scoped_ptr<uint8[]> active_map_;
int active_map_width_;
int active_map_height_;
- base::TimeTicks timestamp_base_;
-
- // Buffer for storing the yuv image.
- scoped_ptr<uint8[]> yuv_image_;
DISALLOW_COPY_AND_ASSIGN(VideoEncoderVpx);
};
diff --git a/remoting/codec/video_encoder_vpx_unittest.cc b/remoting/codec/video_encoder_vpx_unittest.cc
index 462af9c247..5d001062cf 100644
--- a/remoting/codec/video_encoder_vpx_unittest.cc
+++ b/remoting/codec/video_encoder_vpx_unittest.cc
@@ -13,19 +13,104 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
-namespace {
-
-const int kIntMax = std::numeric_limits<int>::max();
+namespace remoting {
-} // namespace
+// xRGB pixel colors for use by tests.
+const uint32 kBlueColor = 0x0000ff;
+const uint32 kGreenColor = 0x00ff00;
-namespace remoting {
+// Creates a frame stippled between blue and red pixels, which is useful for
+// lossy/lossless encode and color tests.
+static scoped_ptr<webrtc::DesktopFrame> CreateTestFrame(
+ const webrtc::DesktopSize& frame_size) {
+ scoped_ptr<webrtc::DesktopFrame> frame(
+ new webrtc::BasicDesktopFrame(frame_size));
+ for (int x = 0; x < frame_size.width(); ++x) {
+ for (int y = 0; y < frame_size.height(); ++y) {
+ uint8* pixel_u8 = frame->data() + (y * frame->stride()) +
+ (x * webrtc::DesktopFrame::kBytesPerPixel);
+ *(reinterpret_cast<uint32*>(pixel_u8)) =
+ ((x + y) & 1) ? kGreenColor : kBlueColor;
+ }
+ }
+ return frame.Pass();
+}
-TEST(VideoEncoderVp8Test, TestVideoEncoder) {
+TEST(VideoEncoderVpxTest, TestVp8VideoEncoder) {
scoped_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP8());
TestVideoEncoder(encoder.get(), false);
}
+TEST(VideoEncoderVpxTest, TestVp9VideoEncoder) {
+ scoped_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
+ // VP9 encoder defaults to lossless encode and lossy (I420) color.
+ TestVideoEncoder(encoder.get(), false);
+}
+
+// Test that the VP9 encoder can switch between lossy & lossless encode.
+TEST(VideoEncoderVpxTest, TestVp9VideoEncoderLossyEncode) {
+ scoped_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
+
+ webrtc::DesktopSize frame_size(1024, 768);
+ scoped_ptr<webrtc::DesktopFrame> frame(CreateTestFrame(frame_size));
+ frame->mutable_updated_region()->SetRect(
+ webrtc::DesktopRect::MakeSize(frame_size));
+
+ // Lossy encode the first frame.
+ encoder->SetLosslessEncode(false);
+ scoped_ptr<VideoPacket> lossy_packet = encoder->Encode(*frame);
+
+ // Lossless encode the second frame.
+ encoder->SetLosslessEncode(true);
+ scoped_ptr<VideoPacket> lossless_packet = encoder->Encode(*frame);
+ EXPECT_GT(lossless_packet->data().size(), lossy_packet->data().size());
+
+ // Lossy encode one more frame.
+ encoder->SetLosslessEncode(false);
+ lossy_packet = encoder->Encode(*frame);
+ EXPECT_LT(lossy_packet->data().size(), lossless_packet->data().size());
+}
+
+// Test that the VP9 encoder can switch between lossy & lossless color.
+TEST(VideoEncoderVpxTest, TestVp9VideoEncoderLossyColor) {
+ scoped_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP9());
+
+ webrtc::DesktopSize frame_size(1024, 768);
+ scoped_ptr<webrtc::DesktopFrame> frame(CreateTestFrame(frame_size));
+ frame->mutable_updated_region()->SetRect(
+ webrtc::DesktopRect::MakeSize(frame_size));
+
+ // Lossy encode the first frame.
+ encoder->SetLosslessColor(false);
+ scoped_ptr<VideoPacket> lossy_packet = encoder->Encode(*frame);
+
+ // Lossless encode the second frame.
+ encoder->SetLosslessColor(true);
+ scoped_ptr<VideoPacket> lossless_packet = encoder->Encode(*frame);
+ EXPECT_GT(lossless_packet->data().size(), lossy_packet->data().size());
+
+ // Lossy encode one more frame.
+ encoder->SetLosslessColor(false);
+ lossy_packet = encoder->Encode(*frame);
+ EXPECT_LT(lossy_packet->data().size(), lossless_packet->data().size());
+}
+
+// Test that the VP8 encoder ignores lossless modes without crashing.
+TEST(VideoEncoderVpxTest, TestVp8VideoEncoderIgnoreLossy) {
+ scoped_ptr<VideoEncoderVpx> encoder(VideoEncoderVpx::CreateForVP8());
+
+ webrtc::DesktopSize frame_size(1024, 768);
+ scoped_ptr<webrtc::DesktopFrame> frame(CreateTestFrame(frame_size));
+ frame->mutable_updated_region()->SetRect(
+ webrtc::DesktopRect::MakeSize(frame_size));
+
+ // Encode a frame, to give the encoder a chance to crash if misconfigured.
+ encoder->SetLosslessEncode(true);
+ encoder->SetLosslessColor(true);
+ scoped_ptr<VideoPacket> packet = encoder->Encode(*frame);
+ EXPECT_TRUE(packet);
+}
+
// Test that calling Encode with a differently-sized media::ScreenCaptureData
// does not leak memory.
TEST(VideoEncoderVp8Test, TestSizeChangeNoLeak) {
diff --git a/remoting/host/DEPS b/remoting/host/DEPS
index 9b19fb8629..90d75cab0f 100644
--- a/remoting/host/DEPS
+++ b/remoting/host/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+jingle/glue",
"+net",
"+remoting/codec",
"+remoting/jingle_glue",
diff --git a/remoting/host/branding.cc b/remoting/host/branding.cc
index 840e9822c8..fe95479824 100644
--- a/remoting/host/branding.cc
+++ b/remoting/host/branding.cc
@@ -4,7 +4,7 @@
#include "remoting/host/branding.h"
-#include "base/file_util.h"
+#include "base/base_paths.h"
#include "base/path_service.h"
namespace {
@@ -46,7 +46,7 @@ base::FilePath GetConfigDir() {
#elif defined(OS_MACOSX)
PathService::Get(base::DIR_APP_DATA, &app_data_dir);
#else
- app_data_dir = base::GetHomeDir();
+ PathService::Get(base::DIR_HOME, &app_data_dir);
#endif
return app_data_dir.Append(kConfigDir);
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc
index 469c5b2849..942e643e7c 100644
--- a/remoting/host/chromoting_host.cc
+++ b/remoting/host/chromoting_host.cc
@@ -8,8 +8,10 @@
#include "base/bind.h"
#include "base/callback.h"
+#include "base/command_line.h"
#include "base/message_loop/message_loop_proxy.h"
#include "build/build_config.h"
+#include "jingle/glue/thread_wrapper.h"
#include "remoting/base/constants.h"
#include "remoting/base/logging.h"
#include "remoting/host/chromoting_host_context.h"
@@ -29,6 +31,8 @@ namespace remoting {
namespace {
+const char kEnableVp9SwitchName[] = "enable-vp9";
+
const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
@@ -86,13 +90,15 @@ ChromotingHost::ChromotingHost(
DCHECK(network_task_runner_->BelongsToCurrentThread());
DCHECK(signal_strategy);
- // VP9 encode is not yet supported.
- protocol::CandidateSessionConfig::DisableVideoCodec(
- protocol_config_.get(), protocol::ChannelConfig::CODEC_VP9);
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
+
+ // Enable VP9 if specified on the command-line.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(kEnableVp9SwitchName)) {
+ protocol_config_->EnableVideoCodec(protocol::ChannelConfig::CODEC_VP9);
+ }
if (!desktop_environment_factory_->SupportsAudioCapture()) {
- protocol::CandidateSessionConfig::DisableAudioChannel(
- protocol_config_.get());
+ protocol_config_->DisableAudioChannel();
}
}
@@ -135,6 +141,10 @@ void ChromotingHost::RemoveStatusObserver(HostStatusObserver* observer) {
status_observers_.RemoveObserver(observer);
}
+void ChromotingHost::AddExtension(scoped_ptr<HostExtension> extension) {
+ extensions_.push_back(extension.release());
+}
+
void ChromotingHost::RejectAuthenticatingClient() {
DCHECK(authenticating_client_);
reject_authenticating_client_ = true;
@@ -224,6 +234,19 @@ void ChromotingHost::OnSessionChannelsConnected(ClientSession* client) {
OnClientConnected(client->client_jid()));
}
+void ChromotingHost::OnSessionClientCapabilities(ClientSession* client) {
+ DCHECK(CalledOnValidThread());
+
+ // Create extension sessions from each registered extension for this client.
+ for (HostExtensionList::iterator extension = extensions_.begin();
+ extension != extensions_.end(); ++extension) {
+ scoped_ptr<HostExtensionSession> extension_session =
+ (*extension)->CreateExtensionSession(client);
+ if (extension_session)
+ client->AddExtensionSession(extension_session.Pass());
+ }
+}
+
void ChromotingHost::OnSessionAuthenticationFailed(ClientSession* client) {
DCHECK(CalledOnValidThread());
@@ -314,6 +337,13 @@ void ChromotingHost::OnIncomingSession(
desktop_environment_factory_,
max_session_duration_,
pairing_registry_);
+
+ // Registers capabilities provided by host extensions.
+ for (HostExtensionList::iterator extension = extensions_.begin();
+ extension != extensions_.end(); ++extension) {
+ client->AddHostCapabilities((*extension)->GetCapabilities());
+ }
+
clients_.push_back(client);
}
diff --git a/remoting/host/chromoting_host.h b/remoting/host/chromoting_host.h
index a6642d4bec..73e8442613 100644
--- a/remoting/host/chromoting_host.h
+++ b/remoting/host/chromoting_host.h
@@ -8,14 +8,16 @@
#include <list>
#include <string>
-#include "base/memory/scoped_ptr.h"
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/threading/non_thread_safe.h"
#include "base/threading/thread.h"
#include "net/base/backoff_entry.h"
#include "remoting/host/client_session.h"
+#include "remoting/host/host_extension.h"
#include "remoting/host/host_status_monitor.h"
#include "remoting/host/host_status_observer.h"
#include "remoting/protocol/authenticator.h"
@@ -91,6 +93,9 @@ class ChromotingHost : public base::NonThreadSafe,
virtual void AddStatusObserver(HostStatusObserver* observer) OVERRIDE;
virtual void RemoveStatusObserver(HostStatusObserver* observer) OVERRIDE;
+ // Registers a host extension.
+ void AddExtension(scoped_ptr<HostExtension> extension);
+
// This method may be called only from
// HostStatusObserver::OnClientAuthenticated() to reject the new
// client.
@@ -118,6 +123,7 @@ class ChromotingHost : public base::NonThreadSafe,
virtual void OnSessionAuthenticating(ClientSession* client) OVERRIDE;
virtual bool OnSessionAuthenticated(ClientSession* client) OVERRIDE;
virtual void OnSessionChannelsConnected(ClientSession* client) OVERRIDE;
+ virtual void OnSessionClientCapabilities(ClientSession* client) OVERRIDE;
virtual void OnSessionAuthenticationFailed(ClientSession* client) OVERRIDE;
virtual void OnSessionClosed(ClientSession* session) OVERRIDE;
virtual void OnSessionSequenceNumber(ClientSession* session,
@@ -154,6 +160,7 @@ class ChromotingHost : public base::NonThreadSafe,
friend class ChromotingHostTest;
typedef std::list<ClientSession*> ClientList;
+ typedef ScopedVector<HostExtension> HostExtensionList;
// Immediately disconnects all active clients. Host-internal components may
// shutdown asynchronously, but the caller is guaranteed not to receive
@@ -204,6 +211,9 @@ class ChromotingHost : public base::NonThreadSafe,
// The pairing registry for PIN-less authentication.
scoped_refptr<protocol::PairingRegistry> pairing_registry_;
+ // List of host extensions.
+ HostExtensionList extensions_;
+
base::WeakPtrFactory<ChromotingHost> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ChromotingHost);
diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc
index 2661e0e303..55e4decc1c 100644
--- a/remoting/host/client_session.cc
+++ b/remoting/host/client_session.cc
@@ -18,6 +18,7 @@
#include "remoting/host/audio_capturer.h"
#include "remoting/host/audio_scheduler.h"
#include "remoting/host/desktop_environment.h"
+#include "remoting/host/host_extension_session.h"
#include "remoting/host/input_injector.h"
#include "remoting/host/screen_controls.h"
#include "remoting/host/screen_resolution.h"
@@ -98,6 +99,25 @@ ClientSession::~ClientSession() {
connection_.reset();
}
+void ClientSession::AddExtensionSession(
+ scoped_ptr<HostExtensionSession> extension_session) {
+ DCHECK(CalledOnValidThread());
+
+ extension_sessions_.push_back(extension_session.release());
+}
+
+void ClientSession::AddHostCapabilities(const std::string& capabilities) {
+ DCHECK(CalledOnValidThread());
+
+ if (capabilities.empty())
+ return;
+
+ if (!host_capabilities_.empty())
+ host_capabilities_.append(" ");
+
+ host_capabilities_.append(capabilities);
+}
+
void ClientSession::NotifyClientResolution(
const protocol::ClientResolution& resolution) {
DCHECK(CalledOnValidThread());
@@ -133,6 +153,16 @@ void ClientSession::ControlVideo(const protocol::VideoControl& video_control) {
<< video_control.enable() << ")";
video_scheduler_->Pause(!video_control.enable());
}
+ if (video_control.has_lossless_encode()) {
+ VLOG(1) << "Received VideoControl (lossless_encode="
+ << video_control.lossless_encode() << ")";
+ video_scheduler_->SetLosslessEncode(video_control.lossless_encode());
+ }
+ if (video_control.has_lossless_color()) {
+ VLOG(1) << "Received VideoControl (lossless_color="
+ << video_control.lossless_color() << ")";
+ video_scheduler_->SetLosslessColor(video_control.lossless_color());
+ }
}
void ClientSession::ControlAudio(const protocol::AudioControl& audio_control) {
@@ -168,6 +198,7 @@ void ClientSession::SetCapabilities(
*client_capabilities_ = capabilities.capabilities();
VLOG(1) << "Client capabilities: " << *client_capabilities_;
+ event_handler_->OnSessionClientCapabilities(this);
// Calculate the set of capabilities enabled by both client and host and
// pass it to the desktop environment if it is available.
@@ -204,6 +235,13 @@ void ClientSession::DeliverClientMessage(
HOST_LOG << "gnubby auth is not enabled";
}
return;
+ } else {
+ for(HostExtensionSessionList::iterator it = extension_sessions_.begin();
+ it != extension_sessions_.end(); ++it) {
+ // Extension returns |true| to indicate that the message was handled.
+ if ((*it)->OnExtensionMessage(this, message))
+ return;
+ }
}
}
HOST_LOG << "Unexpected message received: "
@@ -253,7 +291,7 @@ void ClientSession::OnConnectionAuthenticated(
return;
}
- host_capabilities_ = desktop_environment_->GetCapabilities();
+ AddHostCapabilities(desktop_environment_->GetCapabilities());
// Ignore protocol::Capabilities messages from the client if it does not
// support any capabilities.
@@ -261,6 +299,8 @@ void ClientSession::OnConnectionAuthenticated(
VLOG(1) << "The client does not support any capabilities.";
client_capabilities_ = make_scoped_ptr(new std::string());
+ event_handler_->OnSessionClientCapabilities(this);
+
desktop_environment_->SetCapabilities(*client_capabilities_);
}
diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h
index f8923297df..a86839ec58 100644
--- a/remoting/host/client_session.h
+++ b/remoting/host/client_session.h
@@ -8,6 +8,7 @@
#include <string>
#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_vector.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner_helpers.h"
#include "base/threading/non_thread_safe.h"
@@ -15,6 +16,8 @@
#include "base/timer/timer.h"
#include "remoting/host/client_session_control.h"
#include "remoting/host/gnubby_auth_handler.h"
+#include "remoting/host/host_extension.h"
+#include "remoting/host/host_extension_session.h"
#include "remoting/host/mouse_clamping_filter.h"
#include "remoting/host/remote_input_filter.h"
#include "remoting/protocol/clipboard_echo_filter.h"
@@ -64,6 +67,9 @@ class ClientSession
// Called after we've finished connecting all channels.
virtual void OnSessionChannelsConnected(ClientSession* client) = 0;
+ // Called after client has reported capabilities.
+ virtual void OnSessionClientCapabilities(ClientSession* client) = 0;
+
// Called after authentication has failed. Must not tear down this
// object. OnSessionClosed() is notified after this handler
// returns.
@@ -103,6 +109,13 @@ class ClientSession
scoped_refptr<protocol::PairingRegistry> pairing_registry);
virtual ~ClientSession();
+ // Adds an extension to client to handle extension messages.
+ void AddExtensionSession(scoped_ptr<HostExtensionSession> extension_session);
+
+ // Adds extended capabilities to advertise to the client, e.g. those
+ // implemented by |DesktopEnvironment| or |HostExtension|s.
+ void AddHostCapabilities(const std::string& capability);
+
// protocol::HostStub interface.
virtual void NotifyClientResolution(
const protocol::ClientResolution& resolution) OVERRIDE;
@@ -148,7 +161,13 @@ class ClientSession
bool is_authenticated() { return auth_input_filter_.enabled(); }
+ const std::string* client_capabilities() const {
+ return client_capabilities_.get();
+ }
+
private:
+ typedef ScopedVector<HostExtensionSession> HostExtensionSessionList;
+
// Creates a proxy for sending clipboard events to the client.
scoped_ptr<protocol::ClipboardStub> CreateClipboardProxy();
@@ -244,6 +263,9 @@ class ClientSession
// Used to proxy gnubby auth traffic.
scoped_ptr<GnubbyAuthHandler> gnubby_auth_handler_;
+ // Host extension sessions, used to handle extension messages.
+ HostExtensionSessionList extension_sessions_;
+
DISALLOW_COPY_AND_ASSIGN(ClientSession);
};
diff --git a/remoting/host/client_session_unittest.cc b/remoting/host/client_session_unittest.cc
index f28050a9a4..d648ba18b3 100644
--- a/remoting/host/client_session_unittest.cc
+++ b/remoting/host/client_session_unittest.cc
@@ -2,17 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <algorithm>
+#include <string>
+#include <vector>
+
#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
#include "base/test/test_simple_task_runner.h"
#include "remoting/base/auto_thread_task_runner.h"
#include "remoting/base/constants.h"
#include "remoting/host/audio_capturer.h"
#include "remoting/host/client_session.h"
#include "remoting/host/desktop_environment.h"
+#include "remoting/host/host_extension.h"
#include "remoting/host/host_mock_objects.h"
#include "remoting/host/screen_capturer_fake.h"
#include "remoting/protocol/protocol_mock_objects.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
+#include "testing/gmock_mutant.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
@@ -31,9 +38,11 @@ using protocol::SessionConfig;
using testing::_;
using testing::AnyNumber;
using testing::AtMost;
+using testing::CreateFunctor;
using testing::DeleteArg;
using testing::DoAll;
using testing::Expectation;
+using testing::Invoke;
using testing::Return;
using testing::ReturnRef;
using testing::Sequence;
@@ -42,6 +51,8 @@ using testing::StrictMock;
namespace {
+const char kDefaultTestCapability[] = "default";
+
ACTION_P2(InjectClipboardEvent, connection, event) {
connection->clipboard_stub()->InjectClipboardEvent(event);
}
@@ -67,8 +78,75 @@ ACTION_P2(DeliverClientMessage, client_session, message) {
client_session->DeliverClientMessage(message);
}
+ACTION_P2(AddHostCapabilities, client_session, capability) {
+ client_session->AddHostCapabilities(capability);
}
+// Matches a |protocol::Capabilities| argument against a list of capabilities
+// formatted as a space-separated string.
+MATCHER_P(EqCapabilities, expected_capabilities, "") {
+ if (!arg.has_capabilities())
+ return false;
+
+ std::vector<std::string> words_args;
+ std::vector<std::string> words_expected;
+ Tokenize(arg.capabilities(), " ", &words_args);
+ Tokenize(expected_capabilities, " ", &words_expected);
+ std::sort(words_args.begin(), words_args.end());
+ std::sort(words_expected.begin(), words_expected.end());
+ return words_args == words_expected;
+}
+
+// |HostExtension| implementation that can handle an extension message type and
+// provide capabilities.
+class FakeExtension : public HostExtension {
+ public:
+ FakeExtension(const std::string& message_type,
+ const std::string& capabilities);
+ virtual ~FakeExtension();
+
+ virtual std::string GetCapabilities() OVERRIDE;
+ virtual scoped_ptr<HostExtensionSession> CreateExtensionSession(
+ ClientSession* client_session) OVERRIDE;
+
+ bool message_handled() {
+ return message_handled_;
+ }
+
+ private:
+ class FakeExtensionSession : public HostExtensionSession {
+ public:
+ FakeExtensionSession(FakeExtension* extension);
+ virtual ~FakeExtensionSession();
+
+ virtual bool OnExtensionMessage(
+ ClientSession* client_session,
+ const protocol::ExtensionMessage& message) OVERRIDE;
+
+ private:
+ FakeExtension* extension_;
+ };
+
+ std::string message_type_;
+ std::string capabilities_;
+ bool message_handled_;
+};
+
+typedef std::vector<HostExtension*> HostExtensionList;
+
+void CreateExtensionSessions(const HostExtensionList& extensions,
+ ClientSession* client_session) {
+ for (HostExtensionList::const_iterator extension = extensions.begin();
+ extension != extensions.end(); ++extension) {
+ scoped_ptr<HostExtensionSession> extension_session =
+ (*extension)->CreateExtensionSession(client_session);
+ if (extension_session)
+ client_session->AddExtensionSession(extension_session.Pass());
+ }
+}
+
+} // namespace
+
class ClientSessionTest : public testing::Test {
public:
ClientSessionTest() : client_jid_("user@domain/rest-of-jid") {}
@@ -100,6 +178,10 @@ class ClientSessionTest : public testing::Test {
// the input pipe line and starts video capturing.
void ConnectClientSession();
+ // Creates expectations to send an extension message and to disconnect
+ // afterwards.
+ void SetSendMessageAndDisconnectExpectation(const std::string& message_type);
+
// Invoked when the last reference to the AutoThreadTaskRunner has been
// released and quits the message loop to finish the test.
void QuitMainMessageLoop();
@@ -131,6 +213,41 @@ class ClientSessionTest : public testing::Test {
scoped_ptr<MockDesktopEnvironmentFactory> desktop_environment_factory_;
};
+FakeExtension::FakeExtension(const std::string& message_type,
+ const std::string& capabilities)
+ : message_type_(message_type),
+ capabilities_(capabilities),
+ message_handled_(false) {
+}
+
+FakeExtension::~FakeExtension() {}
+
+std::string FakeExtension::GetCapabilities() {
+ return capabilities_;
+}
+
+scoped_ptr<HostExtensionSession> FakeExtension::CreateExtensionSession(
+ ClientSession* client_session) {
+ return scoped_ptr<HostExtensionSession>(new FakeExtensionSession(this));
+}
+
+FakeExtension::FakeExtensionSession::FakeExtensionSession(
+ FakeExtension* extension)
+ : extension_(extension) {
+}
+
+FakeExtension::FakeExtensionSession::~FakeExtensionSession() {}
+
+bool FakeExtension::FakeExtensionSession::OnExtensionMessage(
+ ClientSession* client_session,
+ const protocol::ExtensionMessage& message) {
+ if (message.type() == extension_->message_type_) {
+ extension_->message_handled_ = true;
+ return true;
+ }
+ return false;
+}
+
void ClientSessionTest::SetUp() {
// Arrange to run |message_loop_| until no components depend on it.
scoped_refptr<AutoThreadTaskRunner> ui_task_runner = new AutoThreadTaskRunner(
@@ -180,6 +297,11 @@ void ClientSessionTest::SetUp() {
desktop_environment_factory_.get(),
base::TimeDelta(),
NULL));
+
+ // By default, client will report the same capabilities as the host.
+ EXPECT_CALL(client_stub_, SetCapabilities(_))
+ .Times(AtMost(1))
+ .WillOnce(Invoke(client_session_.get(), &ClientSession::SetCapabilities));
}
void ClientSessionTest::TearDown() {
@@ -211,7 +333,8 @@ DesktopEnvironment* ClientSessionTest::CreateDesktopEnvironment() {
EXPECT_CALL(*desktop_environment, CreateVideoCapturerPtr())
.WillOnce(Invoke(this, &ClientSessionTest::CreateVideoCapturer));
EXPECT_CALL(*desktop_environment, GetCapabilities())
- .Times(AtMost(1));
+ .Times(AtMost(1))
+ .WillOnce(Return(kDefaultTestCapability));
EXPECT_CALL(*desktop_environment, SetCapabilities(_))
.Times(AtMost(1));
@@ -232,6 +355,23 @@ void ClientSessionTest::ConnectClientSession() {
client_session_->OnConnectionChannelsConnected(client_session_->connection());
}
+void ClientSessionTest::SetSendMessageAndDisconnectExpectation(
+ const std::string& message_type) {
+ protocol::ExtensionMessage message;
+ message.set_type(message_type);
+ message.set_data("data");
+
+ Expectation authenticated =
+ EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
+ .WillOnce(Return(true));
+ EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
+ .After(authenticated)
+ .WillOnce(DoAll(
+ DeliverClientMessage(client_session_.get(), message),
+ InvokeWithoutArgs(this, &ClientSessionTest::DisconnectClientSession),
+ InvokeWithoutArgs(this, &ClientSessionTest::StopClientSession)));
+}
+
void ClientSessionTest::QuitMainMessageLoop() {
message_loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
}
@@ -599,4 +739,63 @@ TEST_F(ClientSessionTest, EnableGnubbyAuth) {
message_loop_.Run();
}
+// Verifies that messages can be handled by extensions.
+TEST_F(ClientSessionTest, ExtensionMessages_MessageHandled) {
+ FakeExtension extension1("ext1", "cap1");
+ FakeExtension extension2("ext2", "cap2");
+ FakeExtension extension3("ext3", "cap3");
+ HostExtensionList extensions;
+ extensions.push_back(&extension1);
+ extensions.push_back(&extension2);
+ extensions.push_back(&extension3);
+
+ EXPECT_CALL(session_event_handler_, OnSessionClientCapabilities(_))
+ .WillOnce(Invoke(CreateFunctor(&CreateExtensionSessions, extensions)));
+
+ SetSendMessageAndDisconnectExpectation("ext2");
+ ConnectClientSession();
+ message_loop_.Run();
+
+ EXPECT_FALSE(extension1.message_handled());
+ EXPECT_TRUE(extension2.message_handled());
+ EXPECT_FALSE(extension3.message_handled());
+}
+
+// Verifies that extension messages not handled by extensions don't result in a
+// crash.
+TEST_F(ClientSessionTest, ExtensionMessages_MessageNotHandled) {
+ FakeExtension extension1("ext1", "cap1");
+ HostExtensionList extensions;
+ extensions.push_back(&extension1);
+
+ EXPECT_CALL(session_event_handler_, OnSessionClientCapabilities(_))
+ .WillOnce(Invoke(CreateFunctor(&CreateExtensionSessions, extensions)));
+
+ SetSendMessageAndDisconnectExpectation("extX");
+ ConnectClientSession();
+ message_loop_.Run();
+
+ EXPECT_FALSE(extension1.message_handled());
+}
+
+TEST_F(ClientSessionTest, ReportCapabilities) {
+ Expectation authenticated =
+ EXPECT_CALL(session_event_handler_, OnSessionAuthenticated(_))
+ .WillOnce(DoAll(
+ AddHostCapabilities(client_session_.get(), "capX capZ"),
+ AddHostCapabilities(client_session_.get(), ""),
+ AddHostCapabilities(client_session_.get(), "capY"),
+ Return(true)));
+ EXPECT_CALL(client_stub_,
+ SetCapabilities(EqCapabilities("capX capY capZ default")));
+ EXPECT_CALL(session_event_handler_, OnSessionChannelsConnected(_))
+ .After(authenticated)
+ .WillOnce(DoAll(
+ InvokeWithoutArgs(this, &ClientSessionTest::DisconnectClientSession),
+ InvokeWithoutArgs(this, &ClientSessionTest::StopClientSession)));
+
+ ConnectClientSession();
+ message_loop_.Run();
+}
+
} // namespace remoting
diff --git a/remoting/host/curtain_mode_win.cc b/remoting/host/curtain_mode_win.cc
index d94d61524f..d4d50ea624 100644
--- a/remoting/host/curtain_mode_win.cc
+++ b/remoting/host/curtain_mode_win.cc
@@ -33,7 +33,7 @@ bool CurtainModeWin::Activate() {
DWORD session_id;
if (!ProcessIdToSessionId(GetCurrentProcessId(), &session_id)) {
- LOG_GETLASTERROR(ERROR) << "Failed to map the current PID to session ID";
+ PLOG(ERROR) << "Failed to map the current PID to session ID";
return false;
}
diff --git a/remoting/host/daemon_process.cc b/remoting/host/daemon_process.cc
index 5ec1e8f6fe..9d4ae11831 100644
--- a/remoting/host/daemon_process.cc
+++ b/remoting/host/daemon_process.cc
@@ -259,7 +259,8 @@ void DaemonProcess::CrashNetworkProcess(
void DaemonProcess::Initialize() {
DCHECK(caller_task_runner()->BelongsToCurrentThread());
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
// Get the name of the host configuration file.
base::FilePath default_config_dir = remoting::GetConfigDir();
base::FilePath config_path = default_config_dir.Append(
diff --git a/remoting/host/daemon_process_win.cc b/remoting/host/daemon_process_win.cc
index a03140dbb0..205c9c40ba 100644
--- a/remoting/host/daemon_process_win.cc
+++ b/remoting/host/daemon_process_win.cc
@@ -167,7 +167,7 @@ bool DaemonProcessWin::OnDesktopSessionAgentAttached(
0,
FALSE,
DUPLICATE_SAME_ACCESS)) {
- LOG_GETLASTERROR(ERROR) << "Failed to duplicate the desktop process handle";
+ PLOG(ERROR) << "Failed to duplicate the desktop process handle";
return false;
}
@@ -238,8 +238,7 @@ void DaemonProcessWin::DisableAutoStart() {
OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
if (!scmanager.IsValid()) {
- LOG_GETLASTERROR(INFO)
- << "Failed to connect to the service control manager";
+ PLOG(INFO) << "Failed to connect to the service control manager";
return;
}
@@ -247,8 +246,8 @@ void DaemonProcessWin::DisableAutoStart() {
ScopedScHandle service(
OpenService(scmanager, kWindowsServiceName, desired_access));
if (!service.IsValid()) {
- LOG_GETLASTERROR(INFO)
- << "Failed to open to the '" << kWindowsServiceName << "' service";
+ PLOG(INFO) << "Failed to open to the '" << kWindowsServiceName
+ << "' service";
return;
}
@@ -265,9 +264,8 @@ void DaemonProcessWin::DisableAutoStart() {
NULL,
NULL,
NULL)) {
- LOG_GETLASTERROR(INFO)
- << "Failed to change the '" << kWindowsServiceName
- << "'service start type to 'manual'";
+ PLOG(INFO) << "Failed to change the '" << kWindowsServiceName
+ << "'service start type to 'manual'";
}
}
diff --git a/remoting/host/desktop_process_main.cc b/remoting/host/desktop_process_main.cc
index 51471683be..454e6ebedd 100644
--- a/remoting/host/desktop_process_main.cc
+++ b/remoting/host/desktop_process_main.cc
@@ -23,7 +23,8 @@
namespace remoting {
int DesktopProcessMain() {
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
std::string channel_name =
command_line->GetSwitchValueASCII(kDaemonPipeSwitchName);
diff --git a/remoting/host/desktop_resizer_linux.cc b/remoting/host/desktop_resizer_linux.cc
index 3ebdea30bf..be24adb964 100644
--- a/remoting/host/desktop_resizer_linux.cc
+++ b/remoting/host/desktop_resizer_linux.cc
@@ -160,9 +160,9 @@ DesktopResizerLinux::DesktopResizerLinux()
: display_(XOpenDisplay(NULL)),
screen_(DefaultScreen(display_)),
root_(RootWindow(display_, screen_)),
- exact_resize_(CommandLine::ForCurrentProcess()->
+ exact_resize_(base::CommandLine::ForCurrentProcess()->
HasSwitch("server-supports-exact-resize")) {
- XRRSelectInput (display_, root_, RRScreenChangeNotifyMask);
+ XRRSelectInput(display_, root_, RRScreenChangeNotifyMask);
}
DesktopResizerLinux::~DesktopResizerLinux() {
diff --git a/remoting/host/desktop_session_proxy.cc b/remoting/host/desktop_session_proxy.cc
index d077d5a62d..73497f9ded 100644
--- a/remoting/host/desktop_session_proxy.cc
+++ b/remoting/host/desktop_session_proxy.cc
@@ -233,8 +233,7 @@ bool DesktopSessionProxy::AttachToDesktop(
HANDLE temp_handle;
if (!DuplicateHandle(desktop_process_, desktop_pipe, GetCurrentProcess(),
&temp_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
- LOG_GETLASTERROR(ERROR) << "Failed to duplicate the desktop-to-network"
- " pipe handle";
+ PLOG(ERROR) << "Failed to duplicate the desktop-to-network pipe handle";
desktop_process_ = base::kNullProcessHandle;
base::CloseProcessHandle(desktop_process);
diff --git a/remoting/host/desktop_shape_tracker_win.cc b/remoting/host/desktop_shape_tracker_win.cc
index 8655ed51d6..6b31c0cf08 100644
--- a/remoting/host/desktop_shape_tracker_win.cc
+++ b/remoting/host/desktop_shape_tracker_win.cc
@@ -57,7 +57,7 @@ void DesktopShapeTrackerWin::RefreshDesktopShape() {
// Accumulate a new desktop shape from current window positions.
scoped_ptr<EnumDesktopShapeData> shape_data(new EnumDesktopShapeData);
if (!EnumWindows(EnumWindowsCallback, (LPARAM)shape_data.get())) {
- LOG_GETLASTERROR(ERROR) << "Failed to enumerate windows";
+ PLOG(ERROR) << "Failed to enumerate windows";
desktop_shape_.Clear();
return;
}
diff --git a/remoting/host/heartbeat_sender.cc b/remoting/host/heartbeat_sender.cc
index b489cbe664..61b9370bf1 100644
--- a/remoting/host/heartbeat_sender.cc
+++ b/remoting/host/heartbeat_sender.cc
@@ -14,8 +14,9 @@
#include "base/time/time.h"
#include "remoting/base/constants.h"
#include "remoting/base/logging.h"
-#include "remoting/host/server_log_entry.h"
+#include "remoting/host/server_log_entry_host.h"
#include "remoting/jingle_glue/iq_sender.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
#include "third_party/libjingle/source/talk/xmpp/constants.h"
@@ -255,8 +256,8 @@ scoped_ptr<XmlElement> HeartbeatSender::CreateHeartbeatMessage() {
heartbeat->AddElement(version_tag.release());
// Append log message (which isn't signed).
scoped_ptr<XmlElement> log(ServerLogEntry::MakeStanza());
- scoped_ptr<ServerLogEntry> log_entry(ServerLogEntry::MakeForHeartbeat());
- log_entry->AddHostFields();
+ scoped_ptr<ServerLogEntry> log_entry(MakeLogEntryForHeartbeat());
+ AddHostFieldsToLogEntry(log_entry.get());
log->AddElement(log_entry->ToStanza().release());
heartbeat->AddElement(log.release());
return heartbeat.Pass();
diff --git a/remoting/host/host_event_logger_win.cc b/remoting/host/host_event_logger_win.cc
index a9e22adb60..acd6aaf804 100644
--- a/remoting/host/host_event_logger_win.cc
+++ b/remoting/host/host_event_logger_win.cc
@@ -65,8 +65,7 @@ HostEventLoggerWin::HostEventLoggerWin(base::WeakPtr<HostStatusMonitor> monitor,
if (event_log_ != NULL) {
monitor_->AddStatusObserver(this);
} else {
- LOG_GETLASTERROR(ERROR) << "Failed to register the event source: "
- << application_name;
+ PLOG(ERROR) << "Failed to register the event source: " << application_name;
}
}
@@ -135,7 +134,7 @@ void HostEventLoggerWin::Log(WORD type,
0,
&raw_strings[0],
NULL)) {
- LOG_GETLASTERROR(ERROR) << "Failed to write an event to the event log";
+ PLOG(ERROR) << "Failed to write an event to the event log";
}
}
diff --git a/remoting/host/host_extension.h b/remoting/host/host_extension.h
new file mode 100644
index 0000000000..6014f61588
--- /dev/null
+++ b/remoting/host/host_extension.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_HOST_EXTENSION_H_
+#define REMOTING_HOST_HOST_EXTENSION_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace remoting {
+
+class ClientSession;
+class HostExtensionSession;
+
+// Extends |ChromotingHost| with new functionality, and can use extension
+// messages to communicate with the client.
+class HostExtension {
+ public:
+ virtual ~HostExtension() {}
+
+ // Returns a space-separated list of capabilities provided by this extension.
+ // Capabilities may be used to inform the client of the availability of an
+ // extension. They are merged into the capabilities the host reports to the
+ // client.
+ virtual std::string GetCapabilities() = 0;
+
+ // Creates an extension session, which can handle extension messages for a
+ // client session.
+ // NULL may be returned if |client_session| cannot support this
+ // extension.
+ // |client_session| must outlive the resulting |HostExtensionSession|.
+ virtual scoped_ptr<HostExtensionSession> CreateExtensionSession(
+ ClientSession* client_session) = 0;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_HOST_EXTENSION_H_
diff --git a/remoting/host/host_extension_session.h b/remoting/host/host_extension_session.h
new file mode 100644
index 0000000000..54b9c53e31
--- /dev/null
+++ b/remoting/host/host_extension_session.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_HOST_EXTENSION_SESSION_H_
+#define REMOTING_HOST_HOST_EXTENSION_SESSION_H_
+
+namespace remoting {
+
+namespace protocol {
+class ExtensionMessage;
+} // namespace protocol
+
+class ClientSession;
+
+// Created by an |HostExtension| to store |ClientSession| specific state, and to
+// handle extension messages.
+class HostExtensionSession {
+ public:
+ virtual ~HostExtensionSession() {}
+
+ // Called when the host receives an |ExtensionMessage| for the |ClientSession|
+ // associated with this |HostExtensionSession|.
+ // It returns |true| if the message was handled, and |false| otherwise.
+ virtual bool OnExtensionMessage(
+ ClientSession* client_session,
+ const protocol::ExtensionMessage& message) = 0;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_HOST_EXTENSION_SESSION_H_
+
diff --git a/remoting/host/host_main.cc b/remoting/host/host_main.cc
index 9a19b46fb6..947e4ccc1d 100644
--- a/remoting/host/host_main.cc
+++ b/remoting/host/host_main.cc
@@ -86,27 +86,30 @@ void Usage(const base::FilePath& program_name) {
// Runs the binary specified by the command line, elevated.
int RunElevated() {
- const CommandLine::SwitchMap& switches =
- CommandLine::ForCurrentProcess()->GetSwitches();
- CommandLine::StringVector args = CommandLine::ForCurrentProcess()->GetArgs();
+ const base::CommandLine::SwitchMap& switches =
+ base::CommandLine::ForCurrentProcess()->GetSwitches();
+ base::CommandLine::StringVector args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
// Create the child process command line by copying switches from the current
// command line.
- CommandLine command_line(CommandLine::NO_PROGRAM);
- for (CommandLine::SwitchMap::const_iterator i = switches.begin();
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
+ for (base::CommandLine::SwitchMap::const_iterator i = switches.begin();
i != switches.end(); ++i) {
if (i->first != kElevateSwitchName)
command_line.AppendSwitchNative(i->first, i->second);
}
- for (CommandLine::StringVector::const_iterator i = args.begin();
+ for (base::CommandLine::StringVector::const_iterator i = args.begin();
i != args.end(); ++i) {
command_line.AppendArgNative(*i);
}
// Get the name of the binary to launch.
base::FilePath binary =
- CommandLine::ForCurrentProcess()->GetSwitchValuePath(kElevateSwitchName);
- CommandLine::StringType parameters = command_line.GetCommandLineString();
+ base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
+ kElevateSwitchName);
+ base::CommandLine::StringType parameters =
+ command_line.GetCommandLineString();
// Launch the child process requesting elevation.
SHELLEXECUTEINFO info;
@@ -119,7 +122,7 @@ int RunElevated() {
if (!ShellExecuteEx(&info)) {
DWORD exit_code = GetLastError();
- LOG_GETLASTERROR(ERROR) << "Unable to launch '" << binary.value() << "'";
+ PLOG(ERROR) << "Unable to launch '" << binary.value() << "'";
return exit_code;
}
@@ -157,7 +160,7 @@ int HostMain(int argc, char** argv) {
base::mac::ScopedNSAutoreleasePool pool;
#endif
- CommandLine::Init(argc, argv);
+ base::CommandLine::Init(argc, argv);
// Initialize Breakpad as early as possible. On Mac the command-line needs to
// be initialized first, so that the preference for crash-reporting can be
@@ -184,7 +187,8 @@ int HostMain(int argc, char** argv) {
#endif // defined(OS_WIN)
// Parse the command line.
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kHelpSwitchName) ||
command_line->HasSwitch(kQuestionSwitchName)) {
Usage(command_line->GetProgram());
diff --git a/remoting/host/host_mock_objects.h b/remoting/host/host_mock_objects.h
index d916f04da6..383f47468c 100644
--- a/remoting/host/host_mock_objects.h
+++ b/remoting/host/host_mock_objects.h
@@ -71,6 +71,7 @@ class MockClientSessionEventHandler : public ClientSession::EventHandler {
MOCK_METHOD1(OnSessionAuthenticating, void(ClientSession* client));
MOCK_METHOD1(OnSessionAuthenticated, bool(ClientSession* client));
MOCK_METHOD1(OnSessionChannelsConnected, void(ClientSession* client));
+ MOCK_METHOD1(OnSessionClientCapabilities, void(ClientSession* client));
MOCK_METHOD1(OnSessionAuthenticationFailed, void(ClientSession* client));
MOCK_METHOD1(OnSessionClosed, void(ClientSession* client));
MOCK_METHOD2(OnSessionSequenceNumber, void(ClientSession* client,
diff --git a/remoting/host/host_status_sender.cc b/remoting/host/host_status_sender.cc
index 545fce44a4..44fe98ec23 100644
--- a/remoting/host/host_status_sender.cc
+++ b/remoting/host/host_status_sender.cc
@@ -9,8 +9,9 @@
#include "base/time/time.h"
#include "remoting/base/constants.h"
#include "remoting/base/logging.h"
-#include "remoting/host/server_log_entry.h"
+#include "remoting/host/server_log_entry_host.h"
#include "remoting/jingle_glue/iq_sender.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
#include "third_party/libjingle/source/talk/xmpp/constants.h"
@@ -123,8 +124,8 @@ scoped_ptr<XmlElement> HostStatusSender::CreateHostStatusMessage(
// Append log message (which isn't signed).
scoped_ptr<XmlElement> log(ServerLogEntry::MakeStanza());
scoped_ptr<ServerLogEntry> log_entry(
- ServerLogEntry::MakeForHostStatus(status, exit_code));
- log_entry->AddHostFields();
+ MakeLogEntryForHostStatus(status, exit_code));
+ AddHostFieldsToLogEntry(log_entry.get());
log->AddElement(log_entry->ToStanza().release());
host_status->AddElement(log.release());
return host_status.Pass();
diff --git a/remoting/host/input_injector_win.cc b/remoting/host/input_injector_win.cc
index 235f45e3ec..c40e1508fc 100644
--- a/remoting/host/input_injector_win.cc
+++ b/remoting/host/input_injector_win.cc
@@ -44,7 +44,7 @@ void SendKeyboardInput(uint32_t flags, uint16_t scancode) {
}
if (SendInput(1, &input, sizeof(INPUT)) == 0)
- LOG_GETLASTERROR(ERROR) << "Failed to inject a key event";
+ PLOG(ERROR) << "Failed to inject a key event";
}
using protocol::ClipboardEvent;
@@ -316,7 +316,7 @@ void InputInjectorWin::Core::HandleMouse(const MouseEvent& event) {
if (input.mi.dwFlags) {
if (SendInput(1, &input, sizeof(INPUT)) == 0)
- LOG_GETLASTERROR(ERROR) << "Failed to inject a mouse event";
+ PLOG(ERROR) << "Failed to inject a mouse event";
}
}
diff --git a/remoting/host/ipc_util_win.cc b/remoting/host/ipc_util_win.cc
index a8c28db11f..37c3383087 100644
--- a/remoting/host/ipc_util_win.cc
+++ b/remoting/host/ipc_util_win.cc
@@ -78,7 +78,7 @@ bool CreateConnectedIpcChannel(
FILE_FLAG_OVERLAPPED,
NULL));
if (!client.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'";
+ PLOG(ERROR) << "Failed to connect to '" << pipe_name << "'";
return false;
}
@@ -94,8 +94,8 @@ bool CreateIpcChannel(
// Create security descriptor for the channel.
ScopedSd sd = ConvertSddlToSd(pipe_security_descriptor);
if (!sd) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to create a security descriptor for the Chromoting IPC channel";
+ PLOG(ERROR) << "Failed to create a security descriptor for the Chromoting "
+ "IPC channel";
return false;
}
@@ -121,8 +121,8 @@ bool CreateIpcChannel(
5000,
&security_attributes));
if (!pipe.IsValid()) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to create the server end of the Chromoting IPC channel";
+ PLOG(ERROR)
+ << "Failed to create the server end of the Chromoting IPC channel";
return false;
}
diff --git a/remoting/host/it2me/it2me_host.cc b/remoting/host/it2me/it2me_host.cc
index 505efded07..8081967bb7 100644
--- a/remoting/host/it2me/it2me_host.cc
+++ b/remoting/host/it2me/it2me_host.cc
@@ -21,6 +21,7 @@
#include "remoting/host/register_support_host_request.h"
#include "remoting/host/session_manager_factory.h"
#include "remoting/jingle_glue/network_settings.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/protocol/it2me_host_authenticator_factory.h"
namespace remoting {
@@ -212,11 +213,7 @@ void It2MeHost::FinishConnect() {
// TODO(sergeyu): Add UI to enable it.
scoped_ptr<protocol::CandidateSessionConfig> protocol_config =
protocol::CandidateSessionConfig::CreateDefault();
- protocol::CandidateSessionConfig::DisableAudioChannel(protocol_config.get());
-
- // VP9 encode is not yet supported.
- protocol::CandidateSessionConfig::DisableVideoCodec(
- protocol_config.get(), protocol::ChannelConfig::CODEC_VP9);
+ protocol_config->DisableAudioChannel();
host_->set_protocol_config(protocol_config.Pass());
diff --git a/remoting/host/it2me/it2me_host.h b/remoting/host/it2me/it2me_host.h
index a5d77a42e0..4085d55ed6 100644
--- a/remoting/host/it2me/it2me_host.h
+++ b/remoting/host/it2me/it2me_host.h
@@ -18,12 +18,13 @@ class DictionaryValue;
namespace remoting {
-class RegisterSupportHostRequest;
-class HostNPScriptObject;
-class DesktopEnvironmentFactory;
-class HostEventLogger;
class ChromotingHost;
class ChromotingHostContext;
+class DesktopEnvironmentFactory;
+class HostEventLogger;
+class HostNPScriptObject;
+class RegisterSupportHostRequest;
+class RsaKeyPair;
namespace policy_hack {
diff --git a/remoting/host/it2me/it2me_native_messaging_host_main.cc b/remoting/host/it2me/it2me_native_messaging_host_main.cc
index 1483e04fc4..bec6b3901f 100644
--- a/remoting/host/it2me/it2me_native_messaging_host_main.cc
+++ b/remoting/host/it2me/it2me_native_messaging_host_main.cc
@@ -13,6 +13,7 @@
#include "net/socket/ssl_server_socket.h"
#include "remoting/base/breakpad.h"
#include "remoting/base/resources.h"
+#include "remoting/host/host_exit_codes.h"
#include "remoting/host/it2me/it2me_native_messaging_host.h"
#include "remoting/host/logging.h"
#include "remoting/host/usage_stats_consent.h"
@@ -130,7 +131,7 @@ int It2MeNativeMessagingHostMain(int argc, char** argv) {
// This object instance is required by Chrome code (such as MessageLoop).
base::AtExitManager exit_manager;
- CommandLine::Init(argc, argv);
+ base::CommandLine::Init(argc, argv);
remoting::InitHostLogging();
return StartIt2MeNativeMessagingHost();
diff --git a/remoting/host/linux/linux_me2me_host.py b/remoting/host/linux/linux_me2me_host.py
index 2604849c7a..8adca6e650 100755
--- a/remoting/host/linux/linux_me2me_host.py
+++ b/remoting/host/linux/linux_me2me_host.py
@@ -37,11 +37,16 @@ LOG_FILE_ENV_VAR = "CHROME_REMOTE_DESKTOP_LOG_FILE"
# list of sizes in this environment variable.
DEFAULT_SIZES_ENV_VAR = "CHROME_REMOTE_DESKTOP_DEFAULT_DESKTOP_SIZES"
-# By default, provide a relatively small size to handle the case where resize-
-# to-client is disabled, and a much larger size to support clients with large
-# or mulitple monitors. These defaults can be overridden in ~/.profile.
+# By default, provide a maximum size that is large enough to support clients
+# with large or multiple monitors. This is a comma-separated list of
+# resolutions that will be made available if the X server supports RANDR. These
+# defaults can be overridden in ~/.profile.
DEFAULT_SIZES = "1600x1200,3840x1600"
+# If RANDR is not available, use a smaller default size. Only a single
+# resolution is supported in this case.
+DEFAULT_SIZE_NO_RANDR = "1600x1200"
+
SCRIPT_PATH = sys.path[0]
IS_INSTALLED = (os.path.basename(sys.argv[0]) != 'linux_me2me_host.py')
@@ -78,6 +83,7 @@ MAX_LAUNCH_FAILURES = SHORT_BACKOFF_THRESHOLD + 10
g_desktops = []
g_host_hash = hashlib.md5(socket.gethostname()).hexdigest()
+
def is_supported_platform():
# Always assume that the system is supported if the config directory or
# session file exist.
@@ -89,6 +95,19 @@ def is_supported_platform():
distribution = platform.linux_distribution()
return (distribution[0]).lower() == 'ubuntu'
+
+def get_randr_supporting_x_server():
+ """Returns a path to an X server that supports the RANDR extension, if this
+ is found on the system. Otherwise returns None."""
+ try:
+ xvfb = "/usr/bin/Xvfb-randr"
+ if not os.path.exists(xvfb):
+ xvfb = locate_executable("Xvfb-randr")
+ return xvfb
+ except Exception:
+ return None
+
+
class Config:
def __init__(self, path):
self.path = path
@@ -327,15 +346,10 @@ class Desktop:
max_width = max([width for width, height in self.sizes])
max_height = max([height for width, height in self.sizes])
- try:
- # TODO(jamiewalch): This script expects to be installed alongside
- # Xvfb-randr, but that's no longer the case. Fix this once we have
- # a Xvfb-randr package that installs somewhere sensible.
- xvfb = "/usr/bin/Xvfb-randr"
- if not os.path.exists(xvfb):
- xvfb = locate_executable("Xvfb-randr")
+ xvfb = get_randr_supporting_x_server()
+ if xvfb:
self.server_supports_exact_resize = True
- except Exception:
+ else:
xvfb = "Xvfb"
self.server_supports_exact_resize = False
@@ -1018,9 +1032,15 @@ Web Store: https://chrome.google.com/remotedesktop"""
print >> sys.stderr, EPILOG
return 1
+ # If a RANDR-supporting Xvfb is not available, limit the default size to
+ # something more sensible.
+ if get_randr_supporting_x_server():
+ default_sizes = DEFAULT_SIZES
+ else:
+ default_sizes = DEFAULT_SIZE_NO_RANDR
+
# Collate the list of sizes that XRANDR should support.
if not options.size:
- default_sizes = DEFAULT_SIZES
if os.environ.has_key(DEFAULT_SIZES_ENV_VAR):
default_sizes = os.environ[DEFAULT_SIZES_ENV_VAR]
options.size = default_sizes.split(",")
diff --git a/remoting/host/local_input_monitor_win.cc b/remoting/host/local_input_monitor_win.cc
index c96663bb3b..13bee41e10 100644
--- a/remoting/host/local_input_monitor_win.cc
+++ b/remoting/host/local_input_monitor_win.cc
@@ -128,7 +128,7 @@ void LocalInputMonitorWin::Core::StartOnUiThread() {
window_.reset(new base::win::MessageWindow());
if (!window_->Create(base::Bind(&Core::HandleMessage,
base::Unretained(this)))) {
- LOG_GETLASTERROR(ERROR) << "Failed to create the raw input window";
+ PLOG(ERROR) << "Failed to create the raw input window";
window_.reset();
// If the local input cannot be monitored, the remote user can take over
@@ -168,7 +168,7 @@ LRESULT LocalInputMonitorWin::Core::OnInput(HRAWINPUT input_handle) {
&size,
sizeof(RAWINPUTHEADER));
if (result == -1) {
- LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed";
+ PLOG(ERROR) << "GetRawInputData() failed";
return 0;
}
@@ -181,7 +181,7 @@ LRESULT LocalInputMonitorWin::Core::OnInput(HRAWINPUT input_handle) {
&size,
sizeof(RAWINPUTHEADER));
if (result == -1) {
- LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed";
+ PLOG(ERROR) << "GetRawInputData() failed";
return 0;
}
@@ -217,7 +217,7 @@ bool LocalInputMonitorWin::Core::HandleMessage(
if (RegisterRawInputDevices(&device, 1, sizeof(device))) {
*result = 0;
} else {
- LOG_GETLASTERROR(ERROR) << "RegisterRawInputDevices() failed";
+ PLOG(ERROR) << "RegisterRawInputDevices() failed";
*result = -1;
}
return true;
diff --git a/remoting/host/log_to_server.cc b/remoting/host/log_to_server.cc
index 6799589c8b..8e0dd6bf2a 100644
--- a/remoting/host/log_to_server.cc
+++ b/remoting/host/log_to_server.cc
@@ -8,8 +8,9 @@
#include "base/message_loop/message_loop_proxy.h"
#include "remoting/base/constants.h"
#include "remoting/host/host_status_monitor.h"
-#include "remoting/host/server_log_entry.h"
+#include "remoting/host/server_log_entry_host.h"
#include "remoting/jingle_glue/iq_sender.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "remoting/protocol/transport.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
@@ -43,13 +44,13 @@ void LogToServer::LogSessionStateChange(const std::string& jid,
DCHECK(CalledOnValidThread());
scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(connected));
- entry->AddHostFields();
+ MakeLogEntryForSessionStateChange(connected));
+ AddHostFieldsToLogEntry(entry.get());
entry->AddModeField(mode_);
if (connected) {
DCHECK(connection_route_type_.count(jid) == 1);
- entry->AddConnectionTypeField(connection_route_type_[jid]);
+ AddConnectionTypeToLogEntry(entry.get(), connection_route_type_[jid]);
}
Log(*entry.get());
}
diff --git a/remoting/host/log_to_server.h b/remoting/host/log_to_server.h
index bbd920c441..38f4edb336 100644
--- a/remoting/host/log_to_server.h
+++ b/remoting/host/log_to_server.h
@@ -13,7 +13,7 @@
#include "base/memory/weak_ptr.h"
#include "base/threading/non_thread_safe.h"
#include "remoting/host/host_status_observer.h"
-#include "remoting/host/server_log_entry.h"
+#include "remoting/jingle_glue/server_log_entry.h"
#include "remoting/jingle_glue/signal_strategy.h"
#include "remoting/protocol/transport.h"
diff --git a/remoting/host/oauth_token_getter.cc b/remoting/host/oauth_token_getter.cc
index bf92e95a8c..7ac01c57af 100644
--- a/remoting/host/oauth_token_getter.cc
+++ b/remoting/host/oauth_token_getter.cc
@@ -34,12 +34,16 @@ OAuthTokenGetter::OAuthCredentials::OAuthCredentials(
OAuthTokenGetter::OAuthTokenGetter(
scoped_ptr<OAuthCredentials> oauth_credentials,
- scoped_refptr<net::URLRequestContextGetter> url_request_context_getter)
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
+ bool auto_refresh)
: oauth_credentials_(oauth_credentials.Pass()),
gaia_oauth_client_(
new gaia::GaiaOAuthClient(url_request_context_getter)),
url_request_context_getter_(url_request_context_getter),
refreshing_oauth_token_(false) {
+ if (auto_refresh) {
+ refresh_timer_.reset(new base::OneShotTimer<OAuthTokenGetter>());
+ }
}
OAuthTokenGetter::~OAuthTokenGetter() {}
@@ -58,11 +62,24 @@ void OAuthTokenGetter::OnRefreshTokenResponse(
HOST_LOG << "Received OAuth token.";
oauth_access_token_ = access_token;
- auth_token_expiry_time_ = base::Time::Now() +
+ base::TimeDelta token_expiration =
base::TimeDelta::FromSeconds(expires_seconds) -
base::TimeDelta::FromSeconds(kTokenUpdateTimeBeforeExpirySeconds);
+ auth_token_expiry_time_ = base::Time::Now() + token_expiration;
+
+ if (refresh_timer_) {
+ refresh_timer_->Stop();
+ refresh_timer_->Start(FROM_HERE, token_expiration, this,
+ &OAuthTokenGetter::RefreshOAuthToken);
+ }
- gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this);
+ if (verified_email_.empty()) {
+ gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this);
+ } else {
+ refreshing_oauth_token_ = false;
+ NotifyCallbacks(
+ OAuthTokenGetter::SUCCESS, verified_email_, oauth_access_token_);
+ }
}
void OAuthTokenGetter::OnGetUserEmailResponse(const std::string& user_email) {
@@ -77,6 +94,7 @@ void OAuthTokenGetter::OnGetUserEmailResponse(const std::string& user_email) {
return;
}
+ verified_email_ = user_email;
refreshing_oauth_token_ = false;
// Now that we've refreshed the token and verified that it's for the correct
@@ -100,6 +118,12 @@ void OAuthTokenGetter::OnOAuthError() {
DCHECK(CalledOnValidThread());
LOG(ERROR) << "OAuth: invalid credentials.";
refreshing_oauth_token_ = false;
+
+ // Throw away invalid credentials and force a refresh.
+ oauth_access_token_.clear();
+ auth_token_expiry_time_ = base::Time();
+ verified_email_.clear();
+
NotifyCallbacks(OAuthTokenGetter::AUTH_ERROR, std::string(), std::string());
}
@@ -114,9 +138,10 @@ void OAuthTokenGetter::OnNetworkError(int response_code) {
void OAuthTokenGetter::CallWithToken(const TokenCallback& on_access_token) {
DCHECK(CalledOnValidThread());
- bool need_new_auth_token = oauth_credentials_.get() &&
- (auth_token_expiry_time_.is_null() ||
- base::Time::Now() >= auth_token_expiry_time_);
+ bool need_new_auth_token = auth_token_expiry_time_.is_null() ||
+ base::Time::Now() >= auth_token_expiry_time_ ||
+ verified_email_.empty();
+
if (need_new_auth_token) {
pending_callbacks_.push(on_access_token);
if (!refreshing_oauth_token_)
diff --git a/remoting/host/oauth_token_getter.h b/remoting/host/oauth_token_getter.h
index 4b48a4e68c..a47a3f8267 100644
--- a/remoting/host/oauth_token_getter.h
+++ b/remoting/host/oauth_token_getter.h
@@ -10,6 +10,8 @@
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/threading/non_thread_safe.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
#include "google_apis/gaia/gaia_oauth_client.h"
namespace net {
@@ -56,7 +58,8 @@ class OAuthTokenGetter :
OAuthTokenGetter(
scoped_ptr<OAuthCredentials> oauth_credentials,
- scoped_refptr<net::URLRequestContextGetter> url_request_context_getter);
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
+ bool auto_refresh);
virtual ~OAuthTokenGetter();
// Call |on_access_token| with an access token, or the failure status.
@@ -84,8 +87,10 @@ class OAuthTokenGetter :
bool refreshing_oauth_token_;
std::string oauth_access_token_;
+ std::string verified_email_;
base::Time auth_token_expiry_time_;
std::queue<OAuthTokenGetter::TokenCallback> pending_callbacks_;
+ scoped_ptr<base::OneShotTimer<OAuthTokenGetter> > refresh_timer_;
DISALLOW_COPY_AND_ASSIGN(OAuthTokenGetter);
};
diff --git a/remoting/host/plugin/host_plugin.cc b/remoting/host/plugin/host_plugin.cc
index 16f278aa47..213ea89d78 100644
--- a/remoting/host/plugin/host_plugin.cc
+++ b/remoting/host/plugin/host_plugin.cc
@@ -371,7 +371,7 @@ void InitializePlugin() {
g_at_exit_manager = new base::AtExitManager;
// Init an empty command line for common objects that use it.
- CommandLine::Init(0, NULL);
+ base::CommandLine::Init(0, NULL);
if (remoting::LoadResources("")) {
g_ui_name = new std::string(
@@ -449,7 +449,7 @@ NPError GetValue(NPP instance, NPPVariable variable, void* value) {
// NP_GetValue() can be called before NP_Initialize().
InitializePlugin();
- switch(variable) {
+ switch (variable) {
default:
VLOG(2) << "GetValue - default " << variable;
return NPERR_GENERIC_ERROR;
@@ -533,10 +533,10 @@ EXPORT NPError API_CALL NP_Initialize(NPNetscapeFuncs* npnetscape_funcs
VLOG(2) << "NP_Initialize";
InitializePlugin();
- if(npnetscape_funcs == NULL)
+ if (npnetscape_funcs == NULL)
return NPERR_INVALID_FUNCTABLE_ERROR;
- if(((npnetscape_funcs->version & 0xff00) >> 8) > NP_VERSION_MAJOR)
+ if (((npnetscape_funcs->version & 0xff00) >> 8) > NP_VERSION_MAJOR)
return NPERR_INCOMPATIBLE_VERSION_ERROR;
g_npnetscape_funcs = npnetscape_funcs;
diff --git a/remoting/host/remoting_me2me_host.cc b/remoting/host/remoting_me2me_host.cc
index 5d33b02d7e..22f5da068c 100644
--- a/remoting/host/remoting_me2me_host.cc
+++ b/remoting/host/remoting_me2me_host.cc
@@ -209,7 +209,7 @@ class HostProcess
// Initializes IPC control channel and config file path from |cmd_line|.
// Called on the UI thread.
- bool InitWithCommandLine(const CommandLine* cmd_line);
+ bool InitWithCommandLine(const base::CommandLine* cmd_line);
// Called on the UI thread to start monitoring the configuration file.
void StartWatchingConfigChanges();
@@ -361,7 +361,7 @@ HostProcess::~HostProcess() {
task_runner->DeleteSoon(FROM_HERE, context_.release());
}
-bool HostProcess::InitWithCommandLine(const CommandLine* cmd_line) {
+bool HostProcess::InitWithCommandLine(const base::CommandLine* cmd_line) {
#if defined(REMOTING_MULTI_PROCESS)
// Parse the handle value and convert it to a handle/file descriptor.
std::string channel_name =
@@ -631,7 +631,7 @@ void HostProcess::OnChannelError() {
void HostProcess::StartOnUiThread() {
DCHECK(context_->ui_task_runner()->BelongsToCurrentThread());
- if (!InitWithCommandLine(CommandLine::ForCurrentProcess())) {
+ if (!InitWithCommandLine(base::CommandLine::ForCurrentProcess())) {
// Shutdown the host if the command line is invalid.
context_->network_task_runner()->PostTask(
FROM_HERE, base::Bind(&HostProcess::ShutdownHost, this,
@@ -642,14 +642,14 @@ void HostProcess::StartOnUiThread() {
#if defined(OS_LINUX)
// If an audio pipe is specific on the command-line then initialize
// AudioCapturerLinux to capture from it.
- base::FilePath audio_pipe_name = CommandLine::ForCurrentProcess()->
+ base::FilePath audio_pipe_name = base::CommandLine::ForCurrentProcess()->
GetSwitchValuePath(kAudioPipeSwitchName);
if (!audio_pipe_name.empty()) {
remoting::AudioCapturerLinux::InitializePipeReader(
context_->audio_task_runner(), audio_pipe_name);
}
- base::FilePath gnubby_socket_name = CommandLine::ForCurrentProcess()->
+ base::FilePath gnubby_socket_name = base::CommandLine::ForCurrentProcess()->
GetSwitchValuePath(kAuthSocknameSwitchName);
if (!gnubby_socket_name.empty())
remoting::GnubbyAuthHandler::SetGnubbySocketName(gnubby_socket_name);
@@ -1144,7 +1144,8 @@ void HostProcess::StartHost() {
use_service_account_));
oauth_token_getter_.reset(new OAuthTokenGetter(
- oauth_credentials.Pass(), context_->url_request_context_getter()));
+ oauth_credentials.Pass(), context_->url_request_context_getter(),
+ false));
signaling_connector_->EnableOAuth(oauth_token_getter_.get());
}
diff --git a/remoting/host/sas_injector_win.cc b/remoting/host/sas_injector_win.cc
index 1a866f63f8..c79ae723c2 100644
--- a/remoting/host/sas_injector_win.cc
+++ b/remoting/host/sas_injector_win.cc
@@ -65,8 +65,7 @@ ScopedSoftwareSasPolicy::~ScopedSoftwareSasPolicy() {
LONG result = system_policy_.DeleteValue(kSoftwareSasValueName);
if (result != ERROR_SUCCESS) {
SetLastError(result);
- LOG_GETLASTERROR(ERROR)
- << "Failed to restore the software SAS generation policy";
+ PLOG(ERROR) << "Failed to restore the software SAS generation policy";
}
}
}
@@ -79,8 +78,7 @@ bool ScopedSoftwareSasPolicy::Apply() {
KEY_WOW64_64KEY);
if (result != ERROR_SUCCESS) {
SetLastError(result);
- LOG_GETLASTERROR(ERROR) << "Failed to open 'HKLM\\"
- << kSystemPolicyKeyName << "'";
+ PLOG(ERROR) << "Failed to open 'HKLM\\" << kSystemPolicyKeyName << "'";
return false;
}
@@ -92,8 +90,7 @@ bool ScopedSoftwareSasPolicy::Apply() {
kEnableSoftwareSasByServices);
if (result != ERROR_SUCCESS) {
SetLastError(result);
- LOG_GETLASTERROR(ERROR)
- << "Failed to enable software SAS generation by services";
+ PLOG(ERROR) << "Failed to enable software SAS generation by services";
return false;
} else {
restore_policy_ = true;
@@ -193,22 +190,20 @@ bool SasInjectorXp::InjectSas() {
scoped_ptr<webrtc::Desktop> winlogon_desktop(
webrtc::Desktop::GetDesktop(kWinlogonDesktopName));
if (!winlogon_desktop.get()) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to open '" << kWinlogonDesktopName << "' desktop";
+ PLOG(ERROR) << "Failed to open '" << kWinlogonDesktopName << "' desktop";
return false;
}
webrtc::ScopedThreadDesktop desktop;
if (!desktop.SetThreadDesktop(winlogon_desktop.release())) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to switch to '" << kWinlogonDesktopName << "' desktop";
+ PLOG(ERROR) << "Failed to switch to '" << kWinlogonDesktopName
+ << "' desktop";
return false;
}
HWND window = FindWindow(kSasWindowClassName, kSasWindowTitle);
if (!window) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to find '" << kSasWindowTitle << "' window";
+ PLOG(ERROR) << "Failed to find '" << kSasWindowTitle << "' window";
return false;
}
@@ -216,8 +211,7 @@ bool SasInjectorXp::InjectSas() {
WM_HOTKEY,
0,
MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)) == 0) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to post WM_HOTKEY message";
+ PLOG(ERROR) << "Failed to post WM_HOTKEY message";
return false;
}
diff --git a/remoting/host/server_log_entry.cc b/remoting/host/server_log_entry.cc
deleted file mode 100644
index 58e24b5c7c..0000000000
--- a/remoting/host/server_log_entry.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "remoting/host/server_log_entry.h"
-
-#include "base/logging.h"
-#include "base/strings/stringize_macros.h"
-#include "base/sys_info.h"
-#include "remoting/base/constants.h"
-#include "remoting/protocol/session.h"
-#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
-
-using base::SysInfo;
-using buzz::QName;
-using buzz::XmlElement;
-using remoting::protocol::Session;
-
-namespace remoting {
-
-namespace {
-const char kLogCommand[] = "log";
-
-const char kLogEntry[] = "entry";
-
-const char kKeyEventName[] = "event-name";
-const char kValueEventNameSessionState[] = "session-state";
-const char kValueEventNameHeartbeat[] = "heartbeat";
-const char kValueEventNameHostStatus[] = "host-status";
-
-const char kKeyRole[] = "role";
-const char kValueRoleHost[] = "host";
-
-const char kKeyMode[] = "mode";
-const char kValueModeIt2Me[] = "it2me";
-const char kValueModeMe2Me[] = "me2me";
-
-const char kKeySessionState[] = "session-state";
-const char kValueSessionStateConnected[] = "connected";
-const char kValueSessionStateClosed[] = "closed";
-
-const char kStatusName[] = "status";
-const char kExitCodeName[] = "exit-code";
-
-const char kKeyOsName[] = "os-name";
-
-#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
-const char kKeyOsVersion[] = "os-version";
-#endif
-
-const char kKeyHostVersion[] = "host-version";
-
-const char kKeyCpu[] = "cpu";
-
-const char kKeyConnectionType[] = "connection-type";
-
-} // namespace
-
-ServerLogEntry::ServerLogEntry() {
-}
-
-ServerLogEntry::~ServerLogEntry() {
-}
-
-// static
-scoped_ptr<buzz::XmlElement> ServerLogEntry::MakeStanza() {
- return scoped_ptr<buzz::XmlElement>(
- new XmlElement(QName(kChromotingXmlNamespace, kLogCommand)));
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForSessionStateChange(
- bool connected) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleHost);
- entry->Set(kKeyEventName, kValueEventNameSessionState);
- entry->Set(kKeySessionState, GetValueSessionState(connected));
- return entry.Pass();
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForHeartbeat() {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleHost);
- entry->Set(kKeyEventName, kValueEventNameHeartbeat);
- return entry.Pass();
-}
-
-// static
-scoped_ptr<ServerLogEntry> ServerLogEntry::MakeForHostStatus(
- HostStatusSender::HostStatus host_status, HostExitCodes exit_code) {
- scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
- entry->Set(kKeyRole, kValueRoleHost);
- entry->Set(kKeyEventName, kValueEventNameHostStatus);
- entry->Set(kStatusName, HostStatusSender::HostStatusToString(host_status));
- if (host_status == HostStatusSender::OFFLINE)
- entry->Set(kExitCodeName, ExitCodeToString(exit_code));
- return entry.Pass();
-}
-
-void ServerLogEntry::AddHostFields() {
-#if defined(OS_WIN)
- Set(kKeyOsName, "Windows");
-#elif defined(OS_MACOSX)
- Set(kKeyOsName, "Mac");
-#elif defined(OS_CHROMEOS)
- Set(kKeyOsName, "ChromeOS");
-#elif defined(OS_LINUX)
- Set(kKeyOsName, "Linux");
-#endif
-
- // SysInfo::OperatingSystemVersionNumbers is only defined for the following
- // OSes: see base/sys_info_unittest.cc.
-#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
- std::stringstream os_version;
- int32 os_major_version = 0;
- int32 os_minor_version = 0;
- int32 os_bugfix_version = 0;
- SysInfo::OperatingSystemVersionNumbers(&os_major_version, &os_minor_version,
- &os_bugfix_version);
- os_version << os_major_version << "." << os_minor_version << "."
- << os_bugfix_version;
- Set(kKeyOsVersion, os_version.str());
-#endif
-
- Set(kKeyHostVersion, STRINGIZE(VERSION));
- Set(kKeyCpu, SysInfo::OperatingSystemArchitecture());
-};
-
-void ServerLogEntry::AddModeField(ServerLogEntry::Mode mode) {
- Set(kKeyMode, GetValueMode(mode));
-}
-
-void ServerLogEntry::AddConnectionTypeField(
- protocol::TransportRoute::RouteType type) {
- Set(kKeyConnectionType, protocol::TransportRoute::GetTypeString(type));
-}
-
-// static
-const char* ServerLogEntry::GetValueMode(ServerLogEntry::Mode mode) {
- switch (mode) {
- case IT2ME:
- return kValueModeIt2Me;
- case ME2ME:
- return kValueModeMe2Me;
- default:
- NOTREACHED();
- return NULL;
- }
-}
-
-scoped_ptr<XmlElement> ServerLogEntry::ToStanza() const {
- scoped_ptr<XmlElement> stanza(new XmlElement(QName(
- kChromotingXmlNamespace, kLogEntry)));
- ValuesMap::const_iterator iter;
- for (iter = values_map_.begin(); iter != values_map_.end(); ++iter) {
- stanza->AddAttr(QName(std::string(), iter->first), iter->second);
- }
- return stanza.Pass();
-}
-
-// static
-const char* ServerLogEntry::GetValueSessionState(bool connected) {
- return connected ? kValueSessionStateConnected : kValueSessionStateClosed;
-}
-
-void ServerLogEntry::Set(const std::string& key, const std::string& value) {
- values_map_[key] = value;
-}
-
-} // namespace remoting
diff --git a/remoting/host/server_log_entry.h b/remoting/host/server_log_entry.h
deleted file mode 100644
index ccaa8501bc..0000000000
--- a/remoting/host/server_log_entry.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef REMOTING_HOST_SERVER_LOG_ENTRY_H_
-#define REMOTING_HOST_SERVER_LOG_ENTRY_H_
-
-#include <map>
-#include <string>
-
-#include "base/memory/scoped_ptr.h"
-#include "remoting/host/host_exit_codes.h"
-#include "remoting/host/host_status_sender.h"
-#include "remoting/protocol/transport.h"
-
-namespace buzz {
-class XmlElement;
-} // namespace buzz
-
-namespace remoting {
-
-class ServerLogEntry {
- public:
- // The mode of a connection.
- enum Mode {
- IT2ME,
- ME2ME
- };
-
- // Constructs a log stanza. The caller should add one or more log entry
- // stanzas as children of this stanza, before sending the log stanza to
- // the remoting bot.
- static scoped_ptr<buzz::XmlElement> MakeStanza();
-
- // Constructs a log entry for a session state change.
- // Currently this is either connection or disconnection.
- static scoped_ptr<ServerLogEntry> MakeForSessionStateChange(bool connection);
-
- // Constructs a log entry for a heartbeat.
- static scoped_ptr<ServerLogEntry> MakeForHeartbeat();
-
- // Constructs a log entry for a host status message.
- static scoped_ptr<ServerLogEntry> MakeForHostStatus(
- HostStatusSender::HostStatus host_status, HostExitCodes exit_code);
-
- ~ServerLogEntry();
-
- // Adds fields describing the host to this log entry.
- void AddHostFields();
-
- // Adds a field describing the mode of a connection to this log entry.
- void AddModeField(Mode mode);
-
- // Adds a field describing connection type (direct/stun/relay).
- void AddConnectionTypeField(protocol::TransportRoute::RouteType type);
-
- // Converts this object to an XML stanza.
- scoped_ptr<buzz::XmlElement> ToStanza() const;
-
- private:
- typedef std::map<std::string, std::string> ValuesMap;
-
- ServerLogEntry();
- void Set(const std::string& key, const std::string& value);
-
- static const char* GetValueSessionState(bool connected);
- static const char* GetValueMode(Mode mode);
-
- ValuesMap values_map_;
-};
-
-} // namespace remoting
-
-#endif
diff --git a/remoting/host/server_log_entry_host.cc b/remoting/host/server_log_entry_host.cc
new file mode 100644
index 0000000000..069dc2d384
--- /dev/null
+++ b/remoting/host/server_log_entry_host.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/host/server_log_entry_host.h"
+
+#include "base/strings/stringize_macros.h"
+#include "base/sys_info.h"
+#include "remoting/jingle_glue/server_log_entry.h"
+
+using base::SysInfo;
+
+namespace remoting {
+
+namespace {
+const char kValueEventNameSessionState[] = "session-state";
+const char kValueEventNameHeartbeat[] = "heartbeat";
+const char kValueEventNameHostStatus[] = "host-status";
+
+const char kValueRoleHost[] = "host";
+
+const char kKeySessionState[] = "session-state";
+const char kValueSessionStateConnected[] = "connected";
+const char kValueSessionStateClosed[] = "closed";
+
+const char kStatusName[] = "status";
+const char kExitCodeName[] = "exit-code";
+
+const char kKeyOsName[] = "os-name";
+
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+const char kKeyOsVersion[] = "os-version";
+#endif
+
+const char kKeyHostVersion[] = "host-version";
+
+const char kKeyConnectionType[] = "connection-type";
+
+const char* GetValueSessionState(bool connected) {
+ return connected ? kValueSessionStateConnected : kValueSessionStateClosed;
+}
+
+} // namespace
+
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionStateChange(
+ bool connected) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleHost);
+ entry->AddEventNameField(kValueEventNameSessionState);
+ entry->Set(kKeySessionState, GetValueSessionState(connected));
+ return entry.Pass();
+}
+
+scoped_ptr<ServerLogEntry> MakeLogEntryForHeartbeat() {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleHost);
+ entry->AddEventNameField(kValueEventNameHeartbeat);
+ return entry.Pass();
+}
+
+// static
+scoped_ptr<ServerLogEntry> MakeLogEntryForHostStatus(
+ HostStatusSender::HostStatus host_status, HostExitCodes exit_code) {
+ scoped_ptr<ServerLogEntry> entry(new ServerLogEntry());
+ entry->AddRoleField(kValueRoleHost);
+ entry->AddEventNameField(kValueEventNameHostStatus);
+ entry->Set(kStatusName, HostStatusSender::HostStatusToString(host_status));
+ if (host_status == HostStatusSender::OFFLINE)
+ entry->Set(kExitCodeName, ExitCodeToString(exit_code));
+ return entry.Pass();
+}
+
+void AddHostFieldsToLogEntry(ServerLogEntry* entry) {
+#if defined(OS_WIN)
+ entry->Set(kKeyOsName, "Windows");
+#elif defined(OS_MACOSX)
+ entry->Set(kKeyOsName, "Mac");
+#elif defined(OS_CHROMEOS)
+ entry->Set(kKeyOsName, "ChromeOS");
+#elif defined(OS_LINUX)
+ entry->Set(kKeyOsName, "Linux");
+#endif
+
+ // SysInfo::OperatingSystemVersionNumbers is only defined for the following
+ // OSes: see base/sys_info_unittest.cc.
+#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
+ std::stringstream os_version;
+ int32 os_major_version = 0;
+ int32 os_minor_version = 0;
+ int32 os_bugfix_version = 0;
+ SysInfo::OperatingSystemVersionNumbers(&os_major_version, &os_minor_version,
+ &os_bugfix_version);
+ os_version << os_major_version << "." << os_minor_version << "."
+ << os_bugfix_version;
+ entry->Set(kKeyOsVersion, os_version.str());
+#endif
+
+ entry->Set(kKeyHostVersion, STRINGIZE(VERSION));
+ entry->AddCpuField();
+};
+
+void AddConnectionTypeToLogEntry(ServerLogEntry* entry,
+ protocol::TransportRoute::RouteType type) {
+ entry->Set(kKeyConnectionType, protocol::TransportRoute::GetTypeString(type));
+}
+
+} // namespace remoting
diff --git a/remoting/host/server_log_entry_host.h b/remoting/host/server_log_entry_host.h
new file mode 100644
index 0000000000..efe7ad3b28
--- /dev/null
+++ b/remoting/host/server_log_entry_host.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_HOST_SERVER_LOG_ENTRY_HOST_H_
+#define REMOTING_HOST_SERVER_LOG_ENTRY_HOST_H_
+
+#include "remoting/host/host_exit_codes.h"
+#include "remoting/host/host_status_sender.h"
+#include "remoting/protocol/transport.h"
+
+namespace remoting {
+
+class ServerLogEntry;
+
+// Constructs a log entry for a session state change.
+// Currently this is either connection or disconnection.
+scoped_ptr<ServerLogEntry> MakeLogEntryForSessionStateChange(
+ bool connected);
+
+// Constructs a log entry for a heartbeat.
+scoped_ptr<ServerLogEntry> MakeLogEntryForHeartbeat();
+
+// Constructs a log entry for a host status message.
+scoped_ptr<ServerLogEntry> MakeLogEntryForHostStatus(
+ HostStatusSender::HostStatus host_status, HostExitCodes exit_code);
+
+// Adds fields describing the host to this log entry.
+void AddHostFieldsToLogEntry(ServerLogEntry* entry);
+
+// Adds a field describing connection type (direct/stun/relay).
+void AddConnectionTypeToLogEntry(ServerLogEntry* entry,
+ protocol::TransportRoute::RouteType type);
+
+} // namespace remoting
+
+#endif // REMOTING_HOST_SERVER_LOG_ENTRY_HOST_H_
diff --git a/remoting/host/server_log_entry_unittest.cc b/remoting/host/server_log_entry_host_unittest.cc
index e2e7853cab..603e2ee7dd 100644
--- a/remoting/host/server_log_entry_unittest.cc
+++ b/remoting/host/server_log_entry_host_unittest.cc
@@ -1,10 +1,12 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/scoped_ptr.h"
#include "base/strings/stringize_macros.h"
-#include "remoting/host/server_log_entry.h"
+#include "remoting/host/server_log_entry_host.h"
+#include "remoting/jingle_glue/server_log_entry.h"
+#include "remoting/jingle_glue/server_log_entry_unittest.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
@@ -13,57 +15,8 @@ using buzz::XmlElement;
namespace remoting {
-class ServerLogEntryTest : public testing::Test {
- protected:
- // Verifies a logging stanza.
- // |keyValuePairs| lists the keys that must have specified values, and |keys|
- // lists the keys that must be present, but may have arbitrary values.
- // There must be no other keys.
- static bool VerifyStanza(
- const std::map<std::string, std::string>& key_value_pairs,
- const std::set<std::string> keys,
- const XmlElement* elem,
- std::string* error) {
- int attrCount = 0;
- for (const XmlAttr* attr = elem->FirstAttr(); attr != NULL;
- attr = attr->NextAttr(), attrCount++) {
- if (attr->Name().Namespace().length() != 0) {
- *error = "attribute has non-empty namespace " +
- attr->Name().Namespace();
- return false;
- }
- const std::string& key = attr->Name().LocalPart();
- const std::string& value = attr->Value();
- std::map<std::string, std::string>::const_iterator iter =
- key_value_pairs.find(key);
- if (iter == key_value_pairs.end()) {
- if (keys.find(key) == keys.end()) {
- *error = "unexpected attribute " + key;
- return false;
- }
- } else {
- if (iter->second != value) {
- *error = "attribute " + key + " has value " + iter->second +
- ": expected " + value;
- return false;
- }
- }
- }
- int attr_count_expected = key_value_pairs.size() + keys.size();
- if (attrCount != attr_count_expected) {
- std::stringstream s;
- s << "stanza has " << attrCount << " keys: expected "
- << attr_count_expected;
- *error = s.str();
- return false;
- }
- return true;
- }
-};
-
-TEST_F(ServerLogEntryTest, MakeForSessionStateChange) {
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(true));
+TEST(ServerLogEntryHostTest, MakeForSessionStateChange) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(true));
scoped_ptr<XmlElement> stanza = entry->ToStanza();
std::string error;
std::map<std::string, std::string> key_value_pairs;
@@ -75,8 +28,8 @@ TEST_F(ServerLogEntryTest, MakeForSessionStateChange) {
<< error;
}
-TEST_F(ServerLogEntryTest, MakeForHeartbeat) {
- scoped_ptr<ServerLogEntry> entry(ServerLogEntry::MakeForHeartbeat());
+TEST(ServerLogEntryHostTest, MakeForHeartbeat) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForHeartbeat());
scoped_ptr<XmlElement> stanza = entry->ToStanza();
std::string error;
std::map<std::string, std::string> key_value_pairs;
@@ -87,10 +40,9 @@ TEST_F(ServerLogEntryTest, MakeForHeartbeat) {
<< error;
}
-TEST_F(ServerLogEntryTest, AddHostFields) {
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(true));
- entry->AddHostFields();
+TEST(ServerLogEntryHostTest, AddHostFields) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(true));
+ AddHostFieldsToLogEntry(entry.get());
scoped_ptr<XmlElement> stanza = entry->ToStanza();
std::string error;
std::map<std::string, std::string> key_value_pairs;
@@ -116,9 +68,8 @@ TEST_F(ServerLogEntryTest, AddHostFields) {
error;
}
-TEST_F(ServerLogEntryTest, AddModeField1) {
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(true));
+TEST(ServerLogEntryHostTest, AddModeField1) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(true));
entry->AddModeField(ServerLogEntry::IT2ME);
scoped_ptr<XmlElement> stanza = entry->ToStanza();
std::string error;
@@ -132,9 +83,8 @@ TEST_F(ServerLogEntryTest, AddModeField1) {
error;
}
-TEST_F(ServerLogEntryTest, AddModeField2) {
- scoped_ptr<ServerLogEntry> entry(
- ServerLogEntry::MakeForSessionStateChange(true));
+TEST(ServerLogEntryHostTest, AddModeField2) {
+ scoped_ptr<ServerLogEntry> entry(MakeLogEntryForSessionStateChange(true));
entry->AddModeField(ServerLogEntry::ME2ME);
scoped_ptr<XmlElement> stanza = entry->ToStanza();
std::string error;
diff --git a/remoting/host/service_urls.cc b/remoting/host/service_urls.cc
index 8d8f083f93..9c82250534 100644
--- a/remoting/host/service_urls.cc
+++ b/remoting/host/service_urls.cc
@@ -33,7 +33,7 @@ ServiceUrls::ServiceUrls()
directory_bot_jid_(kDirectoryBotJid) {
#if !defined(NDEBUG)
// Allow debug builds to override urls via command line.
- CommandLine* command_line = CommandLine::ForCurrentProcess();
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
CHECK(command_line);
if (command_line->HasSwitch(kDirectoryBaseUrlSwitch)) {
directory_base_url_ = command_line->GetSwitchValueASCII(
diff --git a/remoting/host/setup/daemon_controller_delegate_linux.cc b/remoting/host/setup/daemon_controller_delegate_linux.cc
index 427a572522..5bc659d2dc 100644
--- a/remoting/host/setup/daemon_controller_delegate_linux.cc
+++ b/remoting/host/setup/daemon_controller_delegate_linux.cc
@@ -6,6 +6,7 @@
#include <unistd.h>
+#include "base/base_paths.h"
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
@@ -16,6 +17,7 @@
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/md5.h"
+#include "base/path_service.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/process/process_handle.h"
@@ -55,8 +57,9 @@ std::string GetMd5(const std::string& value) {
base::FilePath GetConfigPath() {
std::string filename = "host#" + GetMd5(net::GetHostName()) + ".json";
- return base::GetHomeDir().
- Append(".config/chrome-remote-desktop").Append(filename);
+ base::FilePath homedir;
+ PathService::Get(base::DIR_HOME, &homedir);
+ return homedir.Append(".config/chrome-remote-desktop").Append(filename);
}
bool GetScriptPath(base::FilePath* result) {
@@ -85,7 +88,7 @@ bool RunHostScriptWithTimeout(
LOG(ERROR) << "GetScriptPath() failed.";
return false;
}
- CommandLine command_line(script_path);
+ base::CommandLine command_line(script_path);
for (unsigned int i = 0; i < args.size(); ++i) {
command_line.AppendArg(args[i]);
}
@@ -132,7 +135,7 @@ DaemonController::State DaemonControllerDelegateLinux::GetState() {
if (!GetScriptPath(&script_path)) {
return DaemonController::STATE_NOT_IMPLEMENTED;
}
- CommandLine command_line(script_path);
+ base::CommandLine command_line(script_path);
command_line.AppendArg("--get-status");
std::string status;
@@ -182,7 +185,7 @@ scoped_ptr<base::DictionaryValue> DaemonControllerDelegateLinux::GetConfig() {
result->SetString(kXmppLoginConfigPath, value);
}
} else {
- result.reset(); // Return NULL in case of error.
+ result.reset(); // Return NULL in case of error.
}
}
@@ -282,7 +285,7 @@ std::string DaemonControllerDelegateLinux::GetVersion() {
if (!GetScriptPath(&script_path)) {
return std::string();
}
- CommandLine command_line(script_path);
+ base::CommandLine command_line(script_path);
command_line.AppendArg("--host-version");
std::string version;
diff --git a/remoting/host/setup/daemon_controller_delegate_win.cc b/remoting/host/setup/daemon_controller_delegate_win.cc
index 4ff65714f4..1f965cdacb 100644
--- a/remoting/host/setup/daemon_controller_delegate_win.cc
+++ b/remoting/host/setup/daemon_controller_delegate_win.cc
@@ -94,8 +94,7 @@ DWORD OpenService(ScopedScHandle* service_out) {
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
if (!scmanager.IsValid()) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to connect to the service control manager";
+ PLOG(ERROR) << "Failed to connect to the service control manager";
return error;
}
@@ -104,8 +103,8 @@ DWORD OpenService(ScopedScHandle* service_out) {
if (!service.IsValid()) {
DWORD error = GetLastError();
if (error != ERROR_SERVICE_DOES_NOT_EXIST) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to open to the '" << kWindowsServiceName << "' service";
+ PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
+ << "' service";
}
return error;
}
@@ -157,9 +156,8 @@ DaemonController::State DaemonControllerDelegateWin::GetState() {
if (::QueryServiceStatus(service, &status)) {
return ConvertToDaemonState(status.dwCurrentState);
} else {
- LOG_GETLASTERROR(ERROR)
- << "Failed to query the state of the '" << kWindowsServiceName
- << "' service";
+ PLOG(ERROR) << "Failed to query the state of the '"
+ << kWindowsServiceName << "' service";
return DaemonController::STATE_UNKNOWN;
}
break;
diff --git a/remoting/host/setup/me2me_native_messaging_host.cc b/remoting/host/setup/me2me_native_messaging_host.cc
index 72076db769..7ceea6b926 100644
--- a/remoting/host/setup/me2me_native_messaging_host.cc
+++ b/remoting/host/setup/me2me_native_messaging_host.cc
@@ -541,8 +541,8 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
ScopedSd sd = ConvertSddlToSd(security_descriptor);
if (!sd) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a security descriptor for the"
- << "Chromoting Me2Me native messaging host.";
+ PLOG(ERROR) << "Failed to create a security descriptor for the"
+ << "Chromoting Me2Me native messaging host.";
OnError();
return;
}
@@ -567,8 +567,7 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
&security_attributes));
if (!delegate_write_handle.IsValid()) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to create named pipe '" << input_pipe_name << "'";
+ PLOG(ERROR) << "Failed to create named pipe '" << input_pipe_name << "'";
OnError();
return;
}
@@ -588,36 +587,38 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
&security_attributes));
if (!delegate_read_handle.IsValid()) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to create named pipe '" << output_pipe_name << "'";
+ PLOG(ERROR) << "Failed to create named pipe '" << output_pipe_name << "'";
OnError();
return;
}
- const CommandLine* current_command_line = CommandLine::ForCurrentProcess();
- const CommandLine::SwitchMap& switches = current_command_line->GetSwitches();
- CommandLine::StringVector args = current_command_line->GetArgs();
+ const base::CommandLine* current_command_line =
+ base::CommandLine::ForCurrentProcess();
+ const base::CommandLine::SwitchMap& switches =
+ current_command_line->GetSwitches();
+ base::CommandLine::StringVector args = current_command_line->GetArgs();
// Create the child process command line by copying switches from the current
// command line.
- CommandLine command_line(CommandLine::NO_PROGRAM);
+ base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
command_line.AppendSwitch(kElevatingSwitchName);
command_line.AppendSwitchASCII(kInputSwitchName, input_pipe_name);
command_line.AppendSwitchASCII(kOutputSwitchName, output_pipe_name);
DCHECK(!current_command_line->HasSwitch(kElevatingSwitchName));
- for (CommandLine::SwitchMap::const_iterator i = switches.begin();
+ for (base::CommandLine::SwitchMap::const_iterator i = switches.begin();
i != switches.end(); ++i) {
command_line.AppendSwitchNative(i->first, i->second);
}
- for (CommandLine::StringVector::const_iterator i = args.begin();
+ for (base::CommandLine::StringVector::const_iterator i = args.begin();
i != args.end(); ++i) {
command_line.AppendArgNative(*i);
}
// Get the name of the binary to launch.
base::FilePath binary = current_command_line->GetProgram();
- CommandLine::StringType parameters = command_line.GetCommandLineString();
+ base::CommandLine::StringType parameters =
+ command_line.GetCommandLineString();
// Launch the child process requesting elevation.
SHELLEXECUTEINFO info;
@@ -631,7 +632,7 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
if (!ShellExecuteEx(&info)) {
DWORD error = ::GetLastError();
- LOG_GETLASTERROR(ERROR) << "Unable to launch '" << binary.value() << "'";
+ PLOG(ERROR) << "Unable to launch '" << binary.value() << "'";
if (error != ERROR_CANCELLED) {
OnError();
}
@@ -641,8 +642,7 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
if (!::ConnectNamedPipe(delegate_write_handle.Get(), NULL)) {
DWORD error = ::GetLastError();
if (error != ERROR_PIPE_CONNECTED) {
- LOG_GETLASTERROR(ERROR) << "Unable to connect '"
- << input_pipe_name << "'";
+ PLOG(ERROR) << "Unable to connect '" << input_pipe_name << "'";
OnError();
return;
}
@@ -651,8 +651,7 @@ void Me2MeNativeMessagingHost::EnsureElevatedHostCreated() {
if (!::ConnectNamedPipe(delegate_read_handle.Get(), NULL)) {
DWORD error = ::GetLastError();
if (error != ERROR_PIPE_CONNECTED) {
- LOG_GETLASTERROR(ERROR) << "Unable to connect '"
- << output_pipe_name << "'";
+ PLOG(ERROR) << "Unable to connect '" << output_pipe_name << "'";
OnError();
return;
}
diff --git a/remoting/host/setup/me2me_native_messaging_host_main.cc b/remoting/host/setup/me2me_native_messaging_host_main.cc
index d164bec28e..e47b244069 100644
--- a/remoting/host/setup/me2me_native_messaging_host_main.cc
+++ b/remoting/host/setup/me2me_native_messaging_host_main.cc
@@ -93,7 +93,8 @@ int StartMe2MeNativeMessagingHost() {
// Pass handle of the native view to the controller so that the UAC prompts
// are focused properly.
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
int64 native_view_handle = 0;
if (command_line->HasSwitch(kParentWindowSwitchName)) {
std::string native_view =
@@ -134,8 +135,7 @@ int StartMe2MeNativeMessagingHost() {
input_pipe_name.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL));
if (!read_file.IsValid()) {
- LOG_GETLASTERROR(ERROR) <<
- "CreateFile failed on '" << input_pipe_name << "'";
+ PLOG(ERROR) << "CreateFile failed on '" << input_pipe_name << "'";
return kInitializationFailed;
}
@@ -143,8 +143,7 @@ int StartMe2MeNativeMessagingHost() {
output_pipe_name.c_str(), GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL));
if (!write_file.IsValid()) {
- LOG_GETLASTERROR(ERROR) <<
- "CreateFile failed on '" << output_pipe_name << "'";
+ PLOG(ERROR) << "CreateFile failed on '" << output_pipe_name << "'";
return kInitializationFailed;
}
} else {
@@ -256,7 +255,7 @@ int Me2MeNativeMessagingHostMain(int argc, char** argv) {
// This object instance is required by Chrome code (such as MessageLoop).
base::AtExitManager exit_manager;
- CommandLine::Init(argc, argv);
+ base::CommandLine::Init(argc, argv);
remoting::InitHostLogging();
return StartMe2MeNativeMessagingHost();
diff --git a/remoting/host/setup/start_host.cc b/remoting/host/setup/start_host.cc
index ab5688dbce..2090a3ae33 100644
--- a/remoting/host/setup/start_host.cc
+++ b/remoting/host/setup/start_host.cc
@@ -86,8 +86,9 @@ void OnDone(HostStarter::Result result) {
int main(int argc, char** argv) {
// google_apis::GetOAuth2ClientID/Secret need a static CommandLine.
- CommandLine::Init(argc, argv);
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ base::CommandLine::Init(argc, argv);
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
std::string host_name = command_line->GetSwitchValueASCII("name");
std::string host_pin = command_line->GetSwitchValueASCII("pin");
diff --git a/remoting/host/usage_stats_consent_mac.cc b/remoting/host/usage_stats_consent_mac.cc
index 9198307922..1a286b7afb 100644
--- a/remoting/host/usage_stats_consent_mac.cc
+++ b/remoting/host/usage_stats_consent_mac.cc
@@ -21,7 +21,7 @@ bool GetUsageStatsConsent(bool* allowed, bool* set_by_policy) {
// Normally, the ConfigFileWatcher class would be used for retrieving config
// settings, but this code needs to execute before Breakpad is initialised,
// which itself should happen as early as possible during startup.
- CommandLine* command_line = CommandLine::ForCurrentProcess();
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kHostConfigSwitchName)) {
base::FilePath config_file_path =
command_line->GetSwitchValuePath(kHostConfigSwitchName);
diff --git a/remoting/host/usage_stats_consent_win.cc b/remoting/host/usage_stats_consent_win.cc
index d06cfb1bee..bacab67a1a 100644
--- a/remoting/host/usage_stats_consent_win.cc
+++ b/remoting/host/usage_stats_consent_win.cc
@@ -86,8 +86,7 @@ bool SetUsageStatsConsent(bool allowed) {
}
}
- LOG_GETLASTERROR(ERROR)
- << "Failed to record the user's consent to crash dump reporting";
+ PLOG(ERROR) << "Failed to record the user's consent to crash dump reporting";
return false;
}
diff --git a/remoting/host/video_scheduler.cc b/remoting/host/video_scheduler.cc
index 7fc1983128..6aee268266 100644
--- a/remoting/host/video_scheduler.cc
+++ b/remoting/host/video_scheduler.cc
@@ -30,6 +30,10 @@ namespace remoting {
// TODO(hclam): Move this value to CaptureScheduler.
static const int kMaxPendingFrames = 2;
+// Interval between empty keep-alive frames. These frames are sent only
+// when there are no real video frames being sent.
+static const int kKeepAlivePacketIntervalMs = 500;
+
VideoScheduler::VideoScheduler(
scoped_refptr<base::SingleThreadTaskRunner> capture_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner,
@@ -70,11 +74,14 @@ void VideoScheduler::OnCaptureCompleted(webrtc::DesktopFrame* frame) {
scoped_ptr<webrtc::DesktopFrame> owned_frame(frame);
- if (frame) {
+ if (owned_frame) {
scheduler_.RecordCaptureTime(
- base::TimeDelta::FromMilliseconds(frame->capture_time_ms()));
+ base::TimeDelta::FromMilliseconds(owned_frame->capture_time_ms()));
}
+ // Even when |frame| is NULL we still need to post it to the encode thread
+ // to make sure frames are freed in the same order they are received and
+ // that we don't start capturing frame n+2 before frame n is freed.
encode_task_runner_->PostTask(
FROM_HERE, base::Bind(&VideoScheduler::EncodeFrame, this,
base::Passed(&owned_frame), sequence_number_));
@@ -123,8 +130,10 @@ void VideoScheduler::Stop() {
cursor_stub_ = NULL;
video_stub_ = NULL;
- capture_task_runner_->PostTask(FROM_HERE,
- base::Bind(&VideoScheduler::StopOnCaptureThread, this));
+ keep_alive_timer_.reset();
+
+ capture_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoScheduler::StopOnCaptureThread, this));
}
void VideoScheduler::Pause(bool pause) {
@@ -156,6 +165,30 @@ void VideoScheduler::UpdateSequenceNumber(int64 sequence_number) {
sequence_number_ = sequence_number;
}
+void VideoScheduler::SetLosslessEncode(bool want_lossless) {
+ if (!encode_task_runner_->BelongsToCurrentThread()) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ encode_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoScheduler::SetLosslessEncode,
+ this, want_lossless));
+ return;
+ }
+
+ encoder_->SetLosslessEncode(want_lossless);
+}
+
+void VideoScheduler::SetLosslessColor(bool want_lossless) {
+ if (!encode_task_runner_->BelongsToCurrentThread()) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ encode_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoScheduler::SetLosslessColor,
+ this, want_lossless));
+ return;
+ }
+
+ encoder_->SetLosslessColor(want_lossless);
+}
+
// Private methods -----------------------------------------------------------
VideoScheduler::~VideoScheduler() {
@@ -172,6 +205,9 @@ void VideoScheduler::StartOnCaptureThread() {
capturer_->Start(this);
capture_timer_.reset(new base::OneShotTimer<VideoScheduler>());
+ keep_alive_timer_.reset(new base::DelayTimer<VideoScheduler>(
+ FROM_HERE, base::TimeDelta::FromMilliseconds(kKeepAlivePacketIntervalMs),
+ this, &VideoScheduler::SendKeepAlivePacket));
// Capture first frame immedately.
CaptureNextFrame();
@@ -249,19 +285,39 @@ void VideoScheduler::SendVideoPacket(scoped_ptr<VideoPacket> packet) {
return;
video_stub_->ProcessVideoPacket(
- packet.Pass(), base::Bind(&VideoScheduler::VideoFrameSentCallback, this));
+ packet.Pass(), base::Bind(&VideoScheduler::OnVideoPacketSent, this));
}
-void VideoScheduler::VideoFrameSentCallback() {
+void VideoScheduler::OnVideoPacketSent() {
DCHECK(network_task_runner_->BelongsToCurrentThread());
if (!video_stub_)
return;
+ keep_alive_timer_->Reset();
+
capture_task_runner_->PostTask(
FROM_HERE, base::Bind(&VideoScheduler::FrameCaptureCompleted, this));
}
+void VideoScheduler::SendKeepAlivePacket() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (!video_stub_)
+ return;
+
+ video_stub_->ProcessVideoPacket(
+ scoped_ptr<VideoPacket>(new VideoPacket()),
+ base::Bind(&VideoScheduler::OnKeepAlivePacketSent, this));
+}
+
+void VideoScheduler::OnKeepAlivePacketSent() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (keep_alive_timer_)
+ keep_alive_timer_->Reset();
+}
+
void VideoScheduler::SendCursorShape(
scoped_ptr<protocol::CursorShapeInfo> cursor_shape) {
DCHECK(network_task_runner_->BelongsToCurrentThread());
@@ -279,14 +335,11 @@ void VideoScheduler::EncodeFrame(
int64 sequence_number) {
DCHECK(encode_task_runner_->BelongsToCurrentThread());
- // If there is nothing to encode then send an empty keep-alive packet.
+ // Drop the frame if there were no changes.
if (!frame || frame->updated_region().is_empty()) {
- scoped_ptr<VideoPacket> packet(new VideoPacket());
- packet->set_client_sequence_number(sequence_number);
- network_task_runner_->PostTask(
- FROM_HERE, base::Bind(&VideoScheduler::SendVideoPacket, this,
- base::Passed(&packet)));
capture_task_runner_->DeleteSoon(FROM_HERE, frame.release());
+ capture_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&VideoScheduler::FrameCaptureCompleted, this));
return;
}
diff --git a/remoting/host/video_scheduler.h b/remoting/host/video_scheduler.h
index f2eabd9744..2fabb75a87 100644
--- a/remoting/host/video_scheduler.h
+++ b/remoting/host/video_scheduler.h
@@ -112,6 +112,11 @@ class VideoScheduler : public base::RefCountedThreadSafe<VideoScheduler>,
// Sequence numbers are used for performance measurements.
void UpdateSequenceNumber(int64 sequence_number);
+ // Sets whether the video encoder should be requested to encode losslessly,
+ // or to use a lossless color space (typically requiring higher bandwidth).
+ void SetLosslessEncode(bool want_lossless);
+ void SetLosslessColor(bool want_lossless);
+
private:
friend class base::RefCountedThreadSafe<VideoScheduler>;
virtual ~VideoScheduler();
@@ -140,7 +145,13 @@ class VideoScheduler : public base::RefCountedThreadSafe<VideoScheduler>,
// Callback passed to |video_stub_| for the last packet in each frame, to
// rate-limit frame captures to network throughput.
- void VideoFrameSentCallback();
+ void OnVideoPacketSent();
+
+ // Called by |keep_alive_timer_|.
+ void SendKeepAlivePacket();
+
+ // Callback for |video_stub_| called after a keep-alive packet is sent.
+ void OnKeepAlivePacketSent();
// Send updated cursor shape to client.
void SendCursorShape(scoped_ptr<protocol::CursorShapeInfo> cursor_shape);
@@ -173,6 +184,10 @@ class VideoScheduler : public base::RefCountedThreadSafe<VideoScheduler>,
// Timer used to schedule CaptureNextFrame().
scoped_ptr<base::OneShotTimer<VideoScheduler> > capture_timer_;
+ // Timer used to ensure that we send empty keep-alive frames to the client
+ // even when the video stream is paused or encoder is busy.
+ scoped_ptr<base::DelayTimer<VideoScheduler> > keep_alive_timer_;
+
// The number of frames being processed, i.e. frames that we are currently
// capturing, encoding or sending. The value is capped at 2 to minimize
// latency.
@@ -187,7 +202,7 @@ class VideoScheduler : public base::RefCountedThreadSafe<VideoScheduler>,
// True if capture of video frames is paused.
bool is_paused_;
- // This is a number updated by client to trace performance.
+ // Number updated by the caller to trace performance.
int64 sequence_number_;
// An object to schedule capturing.
diff --git a/remoting/host/win/com_security.cc b/remoting/host/win/com_security.cc
index 34b3dc4202..9cefaad2d7 100644
--- a/remoting/host/win/com_security.cc
+++ b/remoting/host/win/com_security.cc
@@ -25,7 +25,7 @@ bool InitializeComSecurity(const std::string& security_descriptor,
// Convert the SDDL description into a security descriptor in absolute format.
ScopedSd relative_sd = ConvertSddlToSd(sddl);
if (!relative_sd) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a security descriptor";
+ PLOG(ERROR) << "Failed to create a security descriptor";
return false;
}
ScopedSd absolute_sd;
@@ -35,7 +35,7 @@ bool InitializeComSecurity(const std::string& security_descriptor,
ScopedAcl sacl;
if (!MakeScopedAbsoluteSd(relative_sd, &absolute_sd, &dacl, &group, &owner,
&sacl)) {
- LOG_GETLASTERROR(ERROR) << "MakeScopedAbsoluteSd() failed";
+ PLOG(ERROR) << "MakeScopedAbsoluteSd() failed";
return false;
}
diff --git a/remoting/host/win/elevated_controller.cc b/remoting/host/win/elevated_controller.cc
index d826dc9e84..7da5ab8f29 100644
--- a/remoting/host/win/elevated_controller.cc
+++ b/remoting/host/win/elevated_controller.cc
@@ -108,8 +108,7 @@ HRESULT ReadConfig(const base::FilePath& filename,
if (!file.IsValid()) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to open '" << filename.value() << "'";
+ PLOG(ERROR) << "Failed to open '" << filename.value() << "'";
return HRESULT_FROM_WIN32(error);
}
@@ -117,8 +116,7 @@ HRESULT ReadConfig(const base::FilePath& filename,
DWORD size = kMaxConfigFileSize;
if (!::ReadFile(file, &buffer[0], size, &size, NULL)) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to read '" << filename.value() << "'";
+ PLOG(ERROR) << "Failed to read '" << filename.value() << "'";
return HRESULT_FROM_WIN32(error);
}
@@ -151,8 +149,8 @@ HRESULT WriteConfigFileToTemp(const base::FilePath& filename,
ScopedSd sd = ConvertSddlToSd(security_descriptor);
if (!sd) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR) <<
- "Failed to create a security descriptor for the configuration file";
+ PLOG(ERROR)
+ << "Failed to create a security descriptor for the configuration file";
return HRESULT_FROM_WIN32(error);
}
@@ -174,16 +172,14 @@ HRESULT WriteConfigFileToTemp(const base::FilePath& filename,
if (!file.IsValid()) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to create '" << filename.value() << "'";
+ PLOG(ERROR) << "Failed to create '" << filename.value() << "'";
return HRESULT_FROM_WIN32(error);
}
DWORD written;
if (!WriteFile(file, content, static_cast<DWORD>(length), &written, NULL)) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to write to '" << filename.value() << "'";
+ PLOG(ERROR) << "Failed to write to '" << filename.value() << "'";
return HRESULT_FROM_WIN32(error);
}
@@ -199,9 +195,8 @@ HRESULT MoveConfigFileFromTemp(const base::FilePath& filename) {
filename.value().c_str(),
MOVEFILE_REPLACE_EXISTING)) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to rename '" << tempname.value() << "' to '"
- << filename.value() << "'";
+ PLOG(ERROR) << "Failed to rename '" << tempname.value() << "' to '"
+ << filename.value() << "'";
return HRESULT_FROM_WIN32(error);
}
@@ -389,9 +384,8 @@ STDMETHODIMP ElevatedController::StartDaemon() {
NULL,
NULL)) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to change the '" << kWindowsServiceName
- << "'service start type to 'auto'";
+ PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
+ << "'service start type to 'auto'";
return HRESULT_FROM_WIN32(error);
}
@@ -399,8 +393,8 @@ STDMETHODIMP ElevatedController::StartDaemon() {
if (!StartService(service, 0, NULL)) {
DWORD error = GetLastError();
if (error != ERROR_SERVICE_ALREADY_RUNNING) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to start the '" << kWindowsServiceName << "'service";
+ PLOG(ERROR) << "Failed to start the '" << kWindowsServiceName
+ << "'service";
return HRESULT_FROM_WIN32(error);
}
@@ -429,9 +423,8 @@ STDMETHODIMP ElevatedController::StopDaemon() {
NULL,
NULL)) {
DWORD error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to change the '" << kWindowsServiceName
- << "'service start type to 'manual'";
+ PLOG(ERROR) << "Failed to change the '" << kWindowsServiceName
+ << "'service start type to 'manual'";
return HRESULT_FROM_WIN32(error);
}
@@ -440,8 +433,8 @@ STDMETHODIMP ElevatedController::StopDaemon() {
if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) {
DWORD error = GetLastError();
if (error != ERROR_SERVICE_NOT_ACTIVE) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to stop the '" << kWindowsServiceName << "'service";
+ PLOG(ERROR) << "Failed to stop the '" << kWindowsServiceName
+ << "'service";
return HRESULT_FROM_WIN32(error);
}
}
@@ -512,8 +505,7 @@ HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE));
if (!scmanager.IsValid()) {
error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to connect to the service control manager";
+ PLOG(ERROR) << "Failed to connect to the service control manager";
return HRESULT_FROM_WIN32(error);
}
@@ -524,8 +516,8 @@ HRESULT ElevatedController::OpenService(ScopedScHandle* service_out) {
::OpenServiceW(scmanager, kWindowsServiceName, desired_access));
if (!service.IsValid()) {
error = GetLastError();
- LOG_GETLASTERROR(ERROR)
- << "Failed to open to the '" << kWindowsServiceName << "' service";
+ PLOG(ERROR) << "Failed to open to the '" << kWindowsServiceName
+ << "' service";
return HRESULT_FROM_WIN32(error);
}
diff --git a/remoting/host/win/host_service.cc b/remoting/host/win/host_service.cc
index 8a9ff9e491..7c1197a87c 100644
--- a/remoting/host/win/host_service.cc
+++ b/remoting/host/win/host_service.cc
@@ -65,8 +65,8 @@ HostService* HostService::GetInstance() {
return Singleton<HostService>::get();
}
-bool HostService::InitWithCommandLine(const CommandLine* command_line) {
- CommandLine::StringVector args = command_line->GetArgs();
+bool HostService::InitWithCommandLine(const base::CommandLine* command_line) {
+ base::CommandLine::StringVector args = command_line->GetArgs();
if (!args.empty()) {
LOG(ERROR) << "No positional parameters expected.";
return false;
@@ -224,8 +224,7 @@ int HostService::RunAsService() {
};
if (!StartServiceCtrlDispatcherW(dispatch_table)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to connect to the service control manager";
+ PLOG(ERROR) << "Failed to connect to the service control manager";
return kInitializationFailed;
}
@@ -247,8 +246,7 @@ void HostService::RunAsServiceImpl() {
service_status_handle_ = RegisterServiceCtrlHandlerExW(
kWindowsServiceName, &HostService::ServiceControlHandler, this);
if (service_status_handle_ == 0) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to register the service control handler";
+ PLOG(ERROR) << "Failed to register the service control handler";
return;
}
@@ -262,7 +260,7 @@ void HostService::RunAsServiceImpl() {
SERVICE_ACCEPT_SESSIONCHANGE;
service_status.dwWin32ExitCode = kSuccessExitCode;
if (!SetServiceStatus(service_status_handle_, &service_status)) {
- LOG_GETLASTERROR(ERROR)
+ PLOG(ERROR)
<< "Failed to report service status to the service control manager";
return;
}
@@ -290,7 +288,7 @@ void HostService::RunAsServiceImpl() {
service_status.dwCurrentState = SERVICE_STOPPED;
service_status.dwControlsAccepted = 0;
if (!SetServiceStatus(service_status_handle_, &service_status)) {
- LOG_GETLASTERROR(ERROR)
+ PLOG(ERROR)
<< "Failed to report service status to the service control manager";
return;
}
@@ -317,8 +315,7 @@ int HostService::RunInConsole() {
// Subscribe to Ctrl-C and other console events.
if (!SetConsoleCtrlHandler(&HostService::ConsoleControlHandler, TRUE)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to set console control handler";
+ PLOG(ERROR) << "Failed to set console control handler";
return result;
}
@@ -326,8 +323,7 @@ int HostService::RunInConsole() {
base::win::MessageWindow window;
if (!window.Create(base::Bind(&HostService::HandleMessage,
base::Unretained(this)))) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to create the session notification window";
+ PLOG(ERROR) << "Failed to create the session notification window";
goto cleanup;
}
@@ -437,11 +433,11 @@ VOID WINAPI HostService::ServiceMain(DWORD argc, WCHAR* argv[]) {
int DaemonProcessMain() {
HostService* service = HostService::GetInstance();
- if (!service->InitWithCommandLine(CommandLine::ForCurrentProcess())) {
+ if (!service->InitWithCommandLine(base::CommandLine::ForCurrentProcess())) {
return kUsageExitCode;
}
return service->Run();
}
-} // namespace remoting
+} // namespace remoting
diff --git a/remoting/host/win/launch_process_with_token.cc b/remoting/host/win/launch_process_with_token.cc
index 5e9ad81a0d..8ee09966e9 100644
--- a/remoting/host/win/launch_process_with_token.cc
+++ b/remoting/host/win/launch_process_with_token.cc
@@ -112,7 +112,7 @@ bool ConnectToExecutionServer(uint32 session_id,
}
if (!pipe.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to connect to '" << pipe_name << "'";
+ PLOG(ERROR) << "Failed to connect to '" << pipe_name << "'";
return false;
}
@@ -127,7 +127,7 @@ bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) {
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_DUPLICATE | desired_access,
&temp_handle)) {
- LOG_GETLASTERROR(ERROR) << "Failed to open process token";
+ PLOG(ERROR) << "Failed to open process token";
return false;
}
ScopedHandle process_token(temp_handle);
@@ -138,7 +138,7 @@ bool CopyProcessToken(DWORD desired_access, ScopedHandle* token_out) {
SecurityImpersonation,
TokenPrimary,
&temp_handle)) {
- LOG_GETLASTERROR(ERROR) << "Failed to duplicate the process token";
+ PLOG(ERROR) << "Failed to duplicate the process token";
return false;
}
@@ -160,15 +160,13 @@ bool CreatePrivilegedToken(ScopedHandle* token_out) {
state.PrivilegeCount = 1;
state.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &state.Privileges[0].Luid)) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to lookup the LUID for the SE_TCB_NAME privilege";
+ PLOG(ERROR) << "Failed to lookup the LUID for the SE_TCB_NAME privilege";
return false;
}
// Enable the SE_TCB_NAME privilege.
if (!AdjustTokenPrivileges(privileged_token, FALSE, &state, 0, NULL, 0)) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to enable SE_TCB_NAME privilege in a token";
+ PLOG(ERROR) << "Failed to enable SE_TCB_NAME privilege in a token";
return false;
}
@@ -206,8 +204,8 @@ bool ProcessCreateProcessResponse(DWORD creation_flags,
FALSE,
process_information->dwProcessId);
if (!process_information->hProcess) {
- LOG_GETLASTERROR(ERROR) << "Failed to open the process "
- << process_information->dwProcessId;
+ PLOG(ERROR) << "Failed to open the process "
+ << process_information->dwProcessId;
return false;
}
}
@@ -233,8 +231,8 @@ bool ProcessCreateProcessResponse(DWORD creation_flags,
FALSE,
process_information->dwThreadId);
if (!process_information->hThread) {
- LOG_GETLASTERROR(ERROR) << "Failed to open the thread "
- << process_information->dwThreadId;
+ PLOG(ERROR) << "Failed to open the thread "
+ << process_information->dwThreadId;
return false;
}
}
@@ -242,8 +240,8 @@ bool ProcessCreateProcessResponse(DWORD creation_flags,
// Resume the thread if the caller didn't want to suspend the process.
if ((creation_flags & CREATE_SUSPENDED) == 0) {
if (!ResumeThread(process_information->hThread)) {
- LOG_GETLASTERROR(ERROR) << "Failed to resume the thread "
- << process_information->dwThreadId;
+ PLOG(ERROR) << "Failed to resume the thread "
+ << process_information->dwThreadId;
return false;
}
}
@@ -265,7 +263,7 @@ bool ReceiveCreateProcessResponse(
DWORD bytes;
CreateProcessResponse response;
if (!ReadFile(pipe, &response, sizeof(response), &bytes, NULL)) {
- LOG_GETLASTERROR(ERROR) << "Failed to receive CreateProcessAsUser response";
+ PLOG(ERROR) << "Failed to receive CreateProcessAsUser response";
return false;
}
@@ -289,7 +287,7 @@ bool ReceiveCreateProcessResponse(
bool SendCreateProcessRequest(
HANDLE pipe,
const base::FilePath::StringType& application_name,
- const CommandLine::StringType& command_line,
+ const base::CommandLine::StringType& command_line,
DWORD creation_flags,
const base::char16* desktop_name) {
// |CreateProcessRequest| structure passes the same parameters to
@@ -359,7 +357,7 @@ bool SendCreateProcessRequest(
// Pass the request to create a process in the target session.
DWORD bytes;
if (!WriteFile(pipe, buffer.get(), size, &bytes, NULL)) {
- LOG_GETLASTERROR(ERROR) << "Failed to send CreateProcessAsUser request";
+ PLOG(ERROR) << "Failed to send CreateProcessAsUser request";
return false;
}
@@ -372,11 +370,10 @@ bool SendCreateProcessRequest(
bool CreateRemoteSessionProcess(
uint32 session_id,
const base::FilePath::StringType& application_name,
- const CommandLine::StringType& command_line,
+ const base::CommandLine::StringType& command_line,
DWORD creation_flags,
const base::char16* desktop_name,
- PROCESS_INFORMATION* process_information_out)
-{
+ PROCESS_INFORMATION* process_information_out) {
DCHECK_LT(base::win::GetVersion(), base::win::VERSION_VISTA);
base::win::ScopedHandle pipe;
@@ -401,7 +398,7 @@ bool CreateRemoteSessionProcess(
return true;
}
-} // namespace
+} // namespace
namespace remoting {
@@ -425,8 +422,7 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
return false;
}
if (!ImpersonateLoggedOnUser(privileged_token)) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to impersonate the privileged token";
+ PLOG(ERROR) << "Failed to impersonate the privileged token";
return false;
}
@@ -436,7 +432,7 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
TokenSessionId,
&new_session_id,
sizeof(new_session_id))) {
- LOG_GETLASTERROR(ERROR) << "Failed to change session ID of a token";
+ PLOG(ERROR) << "Failed to change session ID of a token";
// Revert to the default token.
CHECK(RevertToSelf());
@@ -451,7 +447,7 @@ bool CreateSessionToken(uint32 session_id, ScopedHandle* token_out) {
}
bool LaunchProcessWithToken(const base::FilePath& binary,
- const CommandLine::StringType& command_line,
+ const base::CommandLine::StringType& command_line,
HANDLE user_token,
SECURITY_ATTRIBUTES* process_attributes,
SECURITY_ATTRIBUTES* thread_attributes,
@@ -512,8 +508,7 @@ bool LaunchProcessWithToken(const base::FilePath& binary,
}
if (!result) {
- LOG_GETLASTERROR(ERROR) <<
- "Failed to launch a process with a user token";
+ PLOG(ERROR) << "Failed to launch a process with a user token";
return false;
}
@@ -525,4 +520,4 @@ bool LaunchProcessWithToken(const base::FilePath& binary,
return true;
}
-} // namespace remoting
+} // namespace remoting
diff --git a/remoting/host/win/rdp_client_window.cc b/remoting/host/win/rdp_client_window.cc
index fe3da3c74d..9b483b082d 100644
--- a/remoting/host/win/rdp_client_window.cc
+++ b/remoting/host/win/rdp_client_window.cc
@@ -161,7 +161,7 @@ void RdpClientWindow::InjectSas() {
BYTE keyboard_state[kKeyboardStateLength];
if (!GetKeyboardState(keyboard_state)) {
- LOG_GETLASTERROR(ERROR) << "Failed to get the keyboard state.";
+ PLOG(ERROR) << "Failed to get the keyboard state.";
return;
}
diff --git a/remoting/host/win/unprivileged_process_delegate.cc b/remoting/host/win/unprivileged_process_delegate.cc
index 1457bd7038..1f5a174603 100644
--- a/remoting/host/win/unprivileged_process_delegate.cc
+++ b/remoting/host/win/unprivileged_process_delegate.cc
@@ -113,7 +113,7 @@ bool CreateWindowStationAndDesktop(ScopedSid logon_sid,
// Convert the logon SID into a string.
std::string logon_sid_string = ConvertSidToString(logon_sid.get());
if (logon_sid_string.empty()) {
- LOG_GETLASTERROR(ERROR) << "Failed to convert a SID to string";
+ PLOG(ERROR) << "Failed to convert a SID to string";
return false;
}
@@ -134,7 +134,7 @@ bool CreateWindowStationAndDesktop(ScopedSid logon_sid,
ScopedSd desktop_sd = ConvertSddlToSd(desktop_sddl);
ScopedSd window_station_sd = ConvertSddlToSd(window_station_sddl);
if (!desktop_sd || !window_station_sd) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a security descriptor.";
+ PLOG(ERROR) << "Failed to create a security descriptor.";
return false;
}
@@ -169,13 +169,13 @@ bool CreateWindowStationAndDesktop(ScopedSid logon_sid,
base::UTF8ToUTF16(window_station_name).c_str(), window_station_flags,
desired_access, &security_attributes));
if (!handles.window_station()) {
- LOG_GETLASTERROR(ERROR) << "CreateWindowStation() failed";
+ PLOG(ERROR) << "CreateWindowStation() failed";
return false;
}
// Switch to the new window station and create a desktop on it.
if (!SetProcessWindowStation(handles.window_station())) {
- LOG_GETLASTERROR(ERROR) << "SetProcessWindowStation() failed";
+ PLOG(ERROR) << "SetProcessWindowStation() failed";
return false;
}
@@ -196,12 +196,12 @@ bool CreateWindowStationAndDesktop(ScopedSid logon_sid,
// Switch back to the original window station.
if (!SetProcessWindowStation(current_window_station)) {
- LOG_GETLASTERROR(ERROR) << "SetProcessWindowStation() failed";
+ PLOG(ERROR) << "SetProcessWindowStation() failed";
return false;
}
if (!handles.desktop()) {
- LOG_GETLASTERROR(ERROR) << "CreateDesktop() failed";
+ PLOG(ERROR) << "CreateDesktop() failed";
return false;
}
@@ -237,8 +237,7 @@ void UnprivilegedProcessDelegate::LaunchProcess(
// Create a restricted token that will be used to run the worker process.
ScopedHandle token;
if (!CreateRestrictedToken(&token)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to create a restricted LocalService token";
+ PLOG(ERROR) << "Failed to create a restricted LocalService token";
ReportFatalError();
return;
}
@@ -247,7 +246,7 @@ void UnprivilegedProcessDelegate::LaunchProcess(
// and desktop.
ScopedSid logon_sid = GetLogonSid(token);
if (!logon_sid) {
- LOG_GETLASTERROR(ERROR) << "Failed to retrieve the logon SID";
+ PLOG(ERROR) << "Failed to retrieve the logon SID";
ReportFatalError();
return;
}
@@ -256,7 +255,7 @@ void UnprivilegedProcessDelegate::LaunchProcess(
ScopedSd process_sd = ConvertSddlToSd(kWorkerProcessSd);
ScopedSd thread_sd = ConvertSddlToSd(kWorkerThreadSd);
if (!process_sd || !thread_sd) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a security descriptor";
+ PLOG(ERROR) << "Failed to create a security descriptor";
ReportFatalError();
return;
}
@@ -292,14 +291,13 @@ void UnprivilegedProcessDelegate::LaunchProcess(
"%d", reinterpret_cast<ULONG_PTR>(client.Get()));
// Pass the IPC channel via the command line.
- CommandLine command_line(target_command_->argv());
+ base::CommandLine command_line(target_command_->argv());
command_line.AppendSwitchASCII(kDaemonPipeSwitchName, pipe_handle);
// Create our own window station and desktop accessible by |logon_sid|.
WindowStationAndDesktop handles;
if (!CreateWindowStationAndDesktop(logon_sid.Pass(), &handles)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to create a window station and desktop";
+ PLOG(ERROR) << "Failed to create a window station and desktop";
ReportFatalError();
return;
}
@@ -411,7 +409,7 @@ void UnprivilegedProcessDelegate::ReportProcessLaunched(
desired_access,
FALSE,
0)) {
- LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle";
+ PLOG(ERROR) << "Failed to duplicate a handle";
ReportFatalError();
return;
}
@@ -420,4 +418,4 @@ void UnprivilegedProcessDelegate::ReportProcessLaunched(
event_handler_->OnProcessLaunched(limited_handle.Pass());
}
-} // namespace remoting
+} // namespace remoting
diff --git a/remoting/host/win/worker_process_launcher.cc b/remoting/host/win/worker_process_launcher.cc
index 779fdabd91..f7d89ef600 100644
--- a/remoting/host/win/worker_process_launcher.cc
+++ b/remoting/host/win/worker_process_launcher.cc
@@ -171,8 +171,7 @@ void WorkerProcessLauncher::OnObjectSignaled(HANDLE object) {
// Get exit code of the worker process if it is available.
if (!::GetExitCodeProcess(worker_process_, &exit_code_)) {
- LOG_GETLASTERROR(INFO)
- << "Failed to query the exit code of the worker process";
+ PLOG(INFO) << "Failed to query the exit code of the worker process";
exit_code_ = CONTROL_C_EXIT;
}
diff --git a/remoting/host/win/wts_session_process_delegate.cc b/remoting/host/win/wts_session_process_delegate.cc
index f19b37d687..f939abf898 100644
--- a/remoting/host/win/wts_session_process_delegate.cc
+++ b/remoting/host/win/wts_session_process_delegate.cc
@@ -176,7 +176,7 @@ bool WtsSessionProcessDelegate::Core::Initialize(uint32 session_id) {
ScopedHandle job;
job.Set(CreateJobObject(NULL, NULL));
if (!job.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a job object";
+ PLOG(ERROR) << "Failed to create a job object";
return false;
}
@@ -192,7 +192,7 @@ bool WtsSessionProcessDelegate::Core::Initialize(uint32 session_id) {
JobObjectExtendedLimitInformation,
&info,
sizeof(info))) {
- LOG_GETLASTERROR(ERROR) << "Failed to set limits on the job object";
+ PLOG(ERROR) << "Failed to set limits on the job object";
return false;
}
@@ -307,7 +307,7 @@ void WtsSessionProcessDelegate::Core::OnChannelConnected(int32 peer_pid) {
if (launch_elevated_) {
DWORD pid;
if (!get_named_pipe_client_pid_(pipe_, &pid)) {
- LOG_GETLASTERROR(ERROR) << "Failed to retrive PID of the client";
+ PLOG(ERROR) << "Failed to retrive PID of the client";
ReportFatalError();
return;
}
@@ -324,7 +324,7 @@ void WtsSessionProcessDelegate::Core::OnChannelConnected(int32 peer_pid) {
SYNCHRONIZE | PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION;
ScopedHandle worker_process(OpenProcess(desired_access, false, pid));
if (!worker_process.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to open process " << pid;
+ PLOG(ERROR) << "Failed to open process " << pid;
ReportFatalError();
return;
}
@@ -348,7 +348,7 @@ void WtsSessionProcessDelegate::Core::DoLaunchProcess() {
DCHECK(!pipe_.IsValid());
DCHECK(!worker_process_.IsValid());
- CommandLine command_line(target_command_->argv());
+ base::CommandLine command_line(target_command_->argv());
if (launch_elevated_) {
// The job object is not ready. Retry starting the host process later.
if (!job_.IsValid()) {
@@ -408,15 +408,14 @@ void WtsSessionProcessDelegate::Core::DoLaunchProcess() {
if (launch_elevated_) {
if (!AssignProcessToJobObject(job_, worker_process)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to assign the worker to the job object";
+ PLOG(ERROR) << "Failed to assign the worker to the job object";
ReportFatalError();
return;
}
}
if (!ResumeThread(worker_thread)) {
- LOG_GETLASTERROR(ERROR) << "Failed to resume the worker thread";
+ PLOG(ERROR) << "Failed to resume the worker thread";
ReportFatalError();
return;
}
@@ -460,8 +459,7 @@ void WtsSessionProcessDelegate::Core::InitializeJob(
// Register to receive job notifications via the I/O thread's completion port.
if (!base::MessageLoopForIO::current()->RegisterJobObject(job->Get(), this)) {
- LOG_GETLASTERROR(ERROR)
- << "Failed to associate the job object with a completion port";
+ PLOG(ERROR) << "Failed to associate the job object with a completion port";
return;
}
@@ -521,7 +519,7 @@ void WtsSessionProcessDelegate::Core::ReportProcessLaunched(
desired_access,
FALSE,
0)) {
- LOG_GETLASTERROR(ERROR) << "Failed to duplicate a handle";
+ PLOG(ERROR) << "Failed to duplicate a handle";
ReportFatalError();
return;
}
@@ -566,4 +564,4 @@ void WtsSessionProcessDelegate::KillProcess() {
core_->KillProcess();
}
-} // namespace remoting
+} // namespace remoting
diff --git a/remoting/host/win/wts_terminal_monitor.cc b/remoting/host/win/wts_terminal_monitor.cc
index 6c654791fd..705019b5c2 100644
--- a/remoting/host/win/wts_terminal_monitor.cc
+++ b/remoting/host/win/wts_terminal_monitor.cc
@@ -61,7 +61,7 @@ uint32 WtsTerminalMonitor::LookupSessionId(const std::string& terminal_id) {
DWORD session_info_count;
if (!WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &session_info,
&session_info_count)) {
- LOG_GETLASTERROR(ERROR) << "Failed to enumerate all sessions";
+ PLOG(ERROR) << "Failed to enumerate all sessions";
return kInvalidSessionId;
}
for (DWORD i = 0; i < session_info_count; ++i) {
diff --git a/remoting/ios/Chromoting/Base.lproj/Main.storyboard b/remoting/ios/Chromoting/Base.lproj/Main.storyboard
new file mode 100644
index 0000000000..075e459bde
--- /dev/null
+++ b/remoting/ios/Chromoting/Base.lproj/Main.storyboard
@@ -0,0 +1,426 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="5056" systemVersion="13C1021" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES" initialViewController="SPg-Bt-nEe">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
+ </dependencies>
+ <scenes>
+ <!--Host List View Controller - Host List-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController title="Host List" id="BYZ-38-t0r" customClass="HostListViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="8q6-Qi-N0G"/>
+ <viewControllerLayoutGuide type="bottom" id="Edr-Zi-ZAO"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yvd-W1-HFY">
+ <rect key="frame" x="0.0" y="64" width="768" height="60"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="My Computers:" lineBreakMode="wordWrap" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="99o-m0-Qt5">
+ <rect key="frame" x="20" y="20" width="137" height="24"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="137" id="CYZ-XN-3nQ"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="stn-Kg-u2p">
+ <rect key="frame" x="695" y="20" width="53" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <state key="normal" title="Refresh">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="btnRefreshHostListPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="D8z-44-B3d"/>
+ </connections>
+ </button>
+ <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="3O3-op-Q0j">
+ <rect key="frame" x="374" y="20" width="20" height="20"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </activityIndicatorView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="stn-Kg-u2p" firstAttribute="top" secondItem="yvd-W1-HFY" secondAttribute="top" constant="20" id="0uu-5U-bd3"/>
+ <constraint firstAttribute="height" constant="60" id="2Ki-cp-j33"/>
+ <constraint firstItem="99o-m0-Qt5" firstAttribute="leading" secondItem="yvd-W1-HFY" secondAttribute="leading" constant="20" id="D3N-bD-lZN"/>
+ <constraint firstAttribute="centerY" secondItem="3O3-op-Q0j" secondAttribute="centerY" id="DKj-rz-sK7"/>
+ <constraint firstAttribute="centerX" secondItem="3O3-op-Q0j" secondAttribute="centerX" priority="950" id="feB-Gb-RYy"/>
+ <constraint firstItem="3O3-op-Q0j" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="99o-m0-Qt5" secondAttribute="trailing" constant="40" id="mSQ-Zu-AyV"/>
+ <constraint firstAttribute="trailing" secondItem="stn-Kg-u2p" secondAttribute="trailing" constant="20" id="sbm-eR-63m"/>
+ <constraint firstItem="99o-m0-Qt5" firstAttribute="top" secondItem="yvd-W1-HFY" secondAttribute="top" constant="20" id="zOC-CB-52f"/>
+ </constraints>
+ </view>
+ <tableView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="I5O-uS-nev">
+ <rect key="frame" x="0.0" y="124" width="768" height="900"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <color key="separatorColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
+ <view key="tableFooterView" contentMode="scaleToFill" id="nBs-wd-JTu">
+ <rect key="frame" x="0.0" y="66" width="768" height="1"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ <prototypes>
+ <tableViewCell opaque="NO" contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="HostStatusCell" id="YtQ-XN-44a" customClass="HostCell">
+ <rect key="frame" x="0.0" y="22" width="768" height="44"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxY="YES"/>
+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="YtQ-XN-44a" id="kol-nM-8Ua">
+ <rect key="frame" x="0.0" y="0.0" width="735" height="43"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="auj-6D-D06">
+ <rect key="frame" x="20" y="11" width="42" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <accessibility key="accessibilityConfiguration">
+ <accessibilityTraits key="traits" none="YES"/>
+ </accessibility>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="tc8-OT-lCr">
+ <rect key="frame" x="673" y="11" width="42" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <accessibility key="accessibilityConfiguration">
+ <accessibilityTraits key="traits" none="YES"/>
+ </accessibility>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="centerY" secondItem="auj-6D-D06" secondAttribute="centerY" id="bDt-6K-qdQ"/>
+ <constraint firstAttribute="trailing" secondItem="tc8-OT-lCr" secondAttribute="trailing" constant="20" symbolic="YES" id="cux-zd-0M5"/>
+ <constraint firstAttribute="centerY" secondItem="tc8-OT-lCr" secondAttribute="centerY" id="izT-l3-468"/>
+ <constraint firstItem="auj-6D-D06" firstAttribute="leading" secondItem="kol-nM-8Ua" secondAttribute="leading" constant="20" symbolic="YES" id="wgW-bE-xNZ"/>
+ </constraints>
+ </tableViewCellContentView>
+ <connections>
+ <outlet property="labelHostName" destination="auj-6D-D06" id="Caa-yP-G2e"/>
+ <outlet property="labelStatus" destination="tc8-OT-lCr" id="F1r-sQ-TZA"/>
+ <segue destination="lPw-Vc-fSH" kind="push" identifier="ConnectToHost" id="xky-wb-FYc"/>
+ </connections>
+ </tableViewCell>
+ </prototypes>
+ </tableView>
+ <toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uYY-Ar-yyf">
+ <rect key="frame" x="0.0" y="980" width="768" height="44"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
+ <items>
+ <barButtonItem id="Mye-pG-kOa"/>
+ </items>
+ </toolbar>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstAttribute="bottom" secondItem="uYY-Ar-yyf" secondAttribute="bottom" id="8wJ-Mm-mtZ"/>
+ <constraint firstItem="yvd-W1-HFY" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="Ojr-S3-fjw"/>
+ <constraint firstAttribute="trailing" secondItem="uYY-Ar-yyf" secondAttribute="trailing" id="ewc-KI-7Pa"/>
+ <constraint firstItem="Edr-Zi-ZAO" firstAttribute="top" secondItem="I5O-uS-nev" secondAttribute="bottom" id="h7Z-ei-7Ct"/>
+ <constraint firstAttribute="trailing" secondItem="I5O-uS-nev" secondAttribute="trailing" id="kxU-fk-8jC"/>
+ <constraint firstItem="yvd-W1-HFY" firstAttribute="top" secondItem="8q6-Qi-N0G" secondAttribute="bottom" id="lu1-Ap-BQY"/>
+ <constraint firstAttribute="trailing" secondItem="yvd-W1-HFY" secondAttribute="trailing" id="mKI-XH-7PK"/>
+ <constraint firstItem="I5O-uS-nev" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="r8V-M5-UZY"/>
+ <constraint firstItem="I5O-uS-nev" firstAttribute="top" secondItem="yvd-W1-HFY" secondAttribute="bottom" id="u5c-q8-ryn"/>
+ <constraint firstItem="uYY-Ar-yyf" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="wfZ-mJ-Z0K"/>
+ </constraints>
+ </view>
+ <navigationItem key="navigationItem" id="cve-Qh-DjC">
+ <barButtonItem key="rightBarButtonItem" id="86a-bA-RuA">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="XbR-N0-tUY">
+ <rect key="frame" x="462" y="7" width="290" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
+ <state key="normal" title="Button">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="btnAccountPressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="nLJ-sO-y0g"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ </navigationItem>
+ <connections>
+ <outlet property="_btnAccount" destination="XbR-N0-tUY" id="ifY-zm-YC8"/>
+ <outlet property="_refreshActivityIndicator" destination="3O3-op-Q0j" id="Rni-y1-vra"/>
+ <outlet property="_tableHostList" destination="I5O-uS-nev" id="ZxW-4E-eca"/>
+ <outlet property="_versionInfo" destination="Mye-pG-kOa" id="6pV-0Q-bRO"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="389" y="-237"/>
+ </scene>
+ <!--Navigation Controller-->
+ <scene sceneID="Quq-3g-lvI">
+ <objects>
+ <navigationController definesPresentationContext="YES" id="SPg-Bt-nEe" sceneMemberID="viewController">
+ <navigationBar key="navigationBar" contentMode="scaleToFill" id="ajj-SW-dz6">
+ <autoresizingMask key="autoresizingMask"/>
+ </navigationBar>
+ <connections>
+ <segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="iup-TL-tUq"/>
+ </connections>
+ </navigationController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="h9P-fz-hAV" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="-855" y="-100"/>
+ </scene>
+ <!--GLKit View Controller - Host-->
+ <scene sceneID="qZZ-nB-Drp">
+ <objects>
+ <glkViewController autoresizesArchivedViewToFullSize="NO" title="Host" preferredFramesPerSecond="30" id="lPw-Vc-fSH" customClass="HostViewController" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="lC6-3a-Lya"/>
+ <viewControllerLayoutGuide type="bottom" id="is7-yM-cwW"/>
+ </layoutGuides>
+ <glkView key="view" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="MQp-Cd-coc">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <toolbar hidden="YES" opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y3E-aU-nYu">
+ <rect key="frame" x="0.0" y="64" width="768" height="44"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="44" id="kb2-2C-u4O"/>
+ </constraints>
+ <inset key="insetFor6xAndEarlier" minX="0.0" minY="20" maxX="0.0" maxY="-20"/>
+ <items>
+ <barButtonItem style="plain" systemItem="flexibleSpace" id="Yoj-8n-wSI"/>
+ <barButtonItem style="plain" id="DSd-fB-lMy">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="bs6-DO-4n1">
+ <rect key="frame" x="712" y="2" width="40" height="40"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <state key="normal" image="disabled_select.png">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="barBtnToolBarHidePressed:" destination="lPw-Vc-fSH" eventType="touchUpInside" id="4vw-LW-Oor"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ </items>
+ </toolbar>
+ <toolbar clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translucent="NO" translatesAutoresizingMaskIntoConstraints="NO" id="47J-lh-mJo">
+ <rect key="frame" x="0.0" y="20" width="768" height="44"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxY="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <rect key="contentStretch" x="0.0" y="1" width="1" height="1"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="44" id="609-m7-CVM"/>
+ </constraints>
+ <inset key="insetFor6xAndEarlier" minX="0.0" minY="20" maxX="0.0" maxY="-20"/>
+ <items>
+ <barButtonItem id="o9I-36-aO3">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" id="S8p-4Q-pU1">
+ <rect key="frame" x="16" y="-10" width="142" height="64"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <state key="normal" title="Disconnect" image="topbar_button_close.png">
+ <color key="titleColor" red="0.0" green="0.56862747669219971" blue="1" alpha="1" colorSpace="deviceRGB"/>
+ <color key="titleShadowColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="barBtnNavigationBackPressed:" destination="lPw-Vc-fSH" eventType="touchUpInside" id="Nvc-5V-GHl"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ <barButtonItem style="plain" systemItem="flexibleSpace" id="faa-fV-VqT"/>
+ <barButtonItem style="plain" id="UXJ-lQ-dYh">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="top" buttonType="roundedRect" lineBreakMode="middleTruncation" id="gPB-Ng-iZV">
+ <rect key="frame" x="547" y="2" width="40" height="40"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <color key="tintColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
+ <inset key="contentEdgeInsets" minX="0.0" minY="2" maxX="0.0" maxY="-2"/>
+ <state key="normal" image="ic_action_keyboard.png"/>
+ <connections>
+ <action selector="barBtnKeyboardPressed:" destination="lPw-Vc-fSH" eventType="touchUpInside" id="3JJ-Mv-sH5"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ <barButtonItem style="plain" id="cOm-P4-mMd">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" id="jEY-dy-0Fd">
+ <rect key="frame" x="597" y="6" width="73" height="33"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <state key="normal" title="Ctrl+Alt+Del">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="barBtnCtrlAltDelPressed:" destination="lPw-Vc-fSH" eventType="touchUpInside" id="Uua-F9-F2x"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ <barButtonItem style="done" id="ZBR-7V-ycs">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="hFI-xo-JJz">
+ <rect key="frame" x="680" y="11" width="22" height="22"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <state key="normal" image="question_mark.png"/>
+ <connections>
+ <segue destination="sPP-eR-xu8" kind="push" id="sec-Qa-coG"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ <barButtonItem style="plain" id="aIG-mY-fM0">
+ <button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="rjy-Dr-SrF">
+ <rect key="frame" x="712" y="2" width="40" height="40"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <state key="normal" image="disabled_select.png">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="barBtnToolBarHidePressed:" destination="lPw-Vc-fSH" eventType="touchUpInside" id="QRl-Hx-ZE5"/>
+ </connections>
+ </button>
+ </barButtonItem>
+ </items>
+ </toolbar>
+ <view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iuH-L7-odS">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="20"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="20" id="wTH-qy-NyA"/>
+ </constraints>
+ </view>
+ <view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="O2x-II-tL4" customClass="KeyInput">
+ <rect key="frame" x="409" y="168" width="384" height="512"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ <activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="12g-Wu-GoD">
+ <rect key="frame" x="366" y="494" width="37" height="37"/>
+ <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
+ </activityIndicatorView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <gestureRecognizers/>
+ <constraints>
+ <constraint firstItem="iuH-L7-odS" firstAttribute="leading" secondItem="MQp-Cd-coc" secondAttribute="leading" id="4GE-lE-kPa"/>
+ <constraint firstAttribute="trailing" secondItem="Y3E-aU-nYu" secondAttribute="trailing" id="HLQ-QJ-0XZ"/>
+ <constraint firstItem="iuH-L7-odS" firstAttribute="top" secondItem="MQp-Cd-coc" secondAttribute="top" id="Jbb-aA-0gB"/>
+ <constraint firstItem="47J-lh-mJo" firstAttribute="top" secondItem="MQp-Cd-coc" secondAttribute="top" constant="20" id="KBg-4j-gIC"/>
+ <constraint firstAttribute="centerX" secondItem="12g-Wu-GoD" secondAttribute="centerX" id="Oaz-q9-RWE"/>
+ <constraint firstAttribute="trailing" secondItem="iuH-L7-odS" secondAttribute="trailing" id="Y4i-20-9RR"/>
+ <constraint firstItem="Y3E-aU-nYu" firstAttribute="top" secondItem="MQp-Cd-coc" secondAttribute="top" constant="64" id="e68-0C-Yby"/>
+ <constraint firstAttribute="trailing" secondItem="47J-lh-mJo" secondAttribute="trailing" id="jD7-JK-wMe"/>
+ <constraint firstAttribute="centerY" secondItem="12g-Wu-GoD" secondAttribute="centerY" id="oq1-Aj-Sod"/>
+ <constraint firstItem="47J-lh-mJo" firstAttribute="leading" secondItem="MQp-Cd-coc" secondAttribute="leading" id="tpX-fz-QIm"/>
+ <constraint firstItem="Y3E-aU-nYu" firstAttribute="leading" secondItem="MQp-Cd-coc" secondAttribute="leading" id="x9S-Ly-syx"/>
+ </constraints>
+ <connections>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="ghP-cQ-BvF"/>
+ <outletCollection property="gestureRecognizers" destination="WZg-EH-YvF" appends="YES" id="pRl-FP-k2b"/>
+ <outletCollection property="gestureRecognizers" destination="FTW-q5-yDY" appends="YES" id="dMX-MK-3kk"/>
+ <outletCollection property="gestureRecognizers" destination="Y8x-bE-4DN" appends="YES" id="EGt-K1-MHK"/>
+ <outletCollection property="gestureRecognizers" destination="eRF-VD-855" appends="YES" id="O19-1x-Vs6"/>
+ <outletCollection property="gestureRecognizers" destination="To2-UZ-mYz" appends="YES" id="axt-lv-TdE"/>
+ <outletCollection property="gestureRecognizers" destination="zq6-Q8-ZSf" appends="YES" id="6Rt-Pc-3hM"/>
+ <outletCollection property="gestureRecognizers" destination="hmw-Pt-YWc" appends="YES" id="X8V-Ud-DeF"/>
+ </connections>
+ </glkView>
+ <navigationItem key="navigationItem" id="aai-Iq-8wO"/>
+ <connections>
+ <outlet property="_barBtnCtrlAltDel" destination="jEY-dy-0Fd" id="cDD-Jo-I88"/>
+ <outlet property="_barBtnDisconnect" destination="S8p-4Q-pU1" id="bDF-jB-odA"/>
+ <outlet property="_barBtnKeyboard" destination="gPB-Ng-iZV" id="HGO-7O-mlu"/>
+ <outlet property="_barBtnNavigation" destination="rjy-Dr-SrF" id="LHI-Jh-LUh"/>
+ <outlet property="_busyIndicator" destination="12g-Wu-GoD" id="6Jd-v5-9gJ"/>
+ <outlet property="_hiddenToolbar" destination="Y3E-aU-nYu" id="kgE-pB-bd7"/>
+ <outlet property="_hiddenToolbarYPosition" destination="e68-0C-Yby" id="xnJ-vd-PHD"/>
+ <outlet property="_keyEntryView" destination="O2x-II-tL4" id="Tml-cK-8Q8"/>
+ <outlet property="_longPressRecognizer" destination="eRF-VD-855" id="aAD-72-kcg"/>
+ <outlet property="_panRecognizer" destination="Y8x-bE-4DN" id="H0t-Id-tGg"/>
+ <outlet property="_pinchRecognizer" destination="WZg-EH-YvF" id="vw5-rO-RT9"/>
+ <outlet property="_singleTapRecognizer" destination="FTW-q5-yDY" id="zMf-7c-XZA"/>
+ <outlet property="_threeFingerPanRecognizer" destination="hmw-Pt-YWc" id="Gow-7d-bez"/>
+ <outlet property="_threeFingerTapRecognizer" destination="zq6-Q8-ZSf" id="3q9-TT-5ob"/>
+ <outlet property="_toolBarYPosition" destination="KBg-4j-gIC" id="yrM-sN-Rb6"/>
+ <outlet property="_toolbar" destination="47J-lh-mJo" id="rAv-Nw-mQa"/>
+ <outlet property="_twoFingerTapRecognizer" destination="To2-UZ-mYz" id="UhW-3t-nDp"/>
+ </connections>
+ </glkViewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="c72-YN-wvu" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ <pinchGestureRecognizer id="WZg-EH-YvF">
+ <connections>
+ <action selector="pinchGestureTriggered:" destination="lPw-Vc-fSH" id="hD7-CG-6iI"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="gzM-Uv-QwD"/>
+ </connections>
+ </pinchGestureRecognizer>
+ <tapGestureRecognizer id="FTW-q5-yDY">
+ <connections>
+ <action selector="tapGestureTriggered:" destination="lPw-Vc-fSH" id="4kQ-3c-Ocr"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="Ml4-vD-NQc"/>
+ </connections>
+ </tapGestureRecognizer>
+ <panGestureRecognizer minimumNumberOfTouches="1" maximumNumberOfTouches="2" id="Y8x-bE-4DN">
+ <connections>
+ <action selector="panGestureTriggered:" destination="lPw-Vc-fSH" id="hS1-SH-mCS"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="wft-mz-bVm"/>
+ </connections>
+ </panGestureRecognizer>
+ <pongPressGestureRecognizer allowableMovement="10" minimumPressDuration="0.5" id="eRF-VD-855">
+ <connections>
+ <action selector="longPressGestureTriggered:" destination="lPw-Vc-fSH" id="EiW-TC-nqA"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="QHj-hZ-uvi"/>
+ </connections>
+ </pongPressGestureRecognizer>
+ <tapGestureRecognizer numberOfTouchesRequired="2" id="To2-UZ-mYz">
+ <connections>
+ <action selector="twoFingerTapGestureTriggered:" destination="lPw-Vc-fSH" id="I4d-VY-TJU"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="Waj-rg-fv8"/>
+ </connections>
+ </tapGestureRecognizer>
+ <tapGestureRecognizer numberOfTouchesRequired="3" id="zq6-Q8-ZSf">
+ <connections>
+ <action selector="threeFingerTapGestureTriggered:" destination="lPw-Vc-fSH" id="3FN-S9-bka"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="MMX-7P-7nP"/>
+ </connections>
+ </tapGestureRecognizer>
+ <panGestureRecognizer minimumNumberOfTouches="3" maximumNumberOfTouches="3" id="hmw-Pt-YWc">
+ <connections>
+ <action selector="threeFingerPanGestureTriggered:" destination="lPw-Vc-fSH" id="XNo-BN-z61"/>
+ <outlet property="delegate" destination="lPw-Vc-fSH" id="aDd-Ku-dBG"/>
+ </connections>
+ </panGestureRecognizer>
+ </objects>
+ <point key="canvasLocation" x="1424" y="-194"/>
+ </scene>
+ <!--Help View Controller - Help-->
+ <scene sceneID="0IG-kv-azW">
+ <objects>
+ <viewController title="Help" id="sPP-eR-xu8" customClass="HelpViewController" sceneMemberID="viewController">
+ <webView key="view" contentMode="scaleToFill" id="olH-Du-ovq">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
+ </webView>
+ <navigationItem key="navigationItem" title="Help" id="g5b-rq-fEZ">
+ <barButtonItem key="backBarButtonItem" title="Host" id="9oE-DQ-JrR"/>
+ </navigationItem>
+ <connections>
+ <outlet property="_webView" destination="olH-Du-ovq" id="Pit-4U-fB0"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="lLO-Ob-BMh" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="2582" y="-176"/>
+ </scene>
+ </scenes>
+ <resources>
+ <image name="disabled_select.png" width="38" height="16"/>
+ <image name="ic_action_keyboard.png" width="96" height="96"/>
+ <image name="question_mark.png" width="12" height="12"/>
+ <image name="topbar_button_close.png" width="64" height="64"/>
+ </resources>
+ <simulatedMetricsContainer key="defaultSimulatedMetrics">
+ <simulatedStatusBarMetrics key="statusBar" statusBarStyle="blackOpaque"/>
+ <simulatedOrientationMetrics key="orientation"/>
+ <simulatedScreenMetrics key="destination"/>
+ </simulatedMetricsContainer>
+</document>
diff --git a/remoting/ios/Chromoting/Chromoting-Info.plist b/remoting/ios/Chromoting/Chromoting-Info.plist
new file mode 100644
index 0000000000..c2d0be6208
--- /dev/null
+++ b/remoting/ios/Chromoting/Chromoting-Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>net.fusionlabs.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>2.4</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationPortrait</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/.xccurrentversion b/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/.xccurrentversion
new file mode 100644
index 0000000000..86e8cb1fe4
--- /dev/null
+++ b/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>_XCCurrentVersionName</key>
+ <string>ChromotingModel.xcdatamodel</string>
+</dict>
+</plist>
diff --git a/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/ChromotingModel.xcdatamodel/contents b/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/ChromotingModel.xcdatamodel/contents
new file mode 100644
index 0000000000..b8783eb242
--- /dev/null
+++ b/remoting/ios/Chromoting/ChromotingModel.xcdatamodeld/ChromotingModel.xcdatamodel/contents
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="5063" systemVersion="13C64" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
+ <entity name="HostPreferences" representedClassName="HostPreferences" syncable="YES">
+ <attribute name="askForPin" optional="YES" attributeType="Boolean" syncable="YES"/>
+ <attribute name="hostId" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="hostPin" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="pairId" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="pairSecret" optional="YES" attributeType="String" syncable="YES"/>
+ </entity>
+ <elements>
+ <element name="HostPreferences" positionX="0" positionY="0" width="128" height="118"/>
+ </elements>
+</model> \ No newline at end of file
diff --git a/remoting/ios/Chromoting/GTMOAuth2ViewTouch.xib b/remoting/ios/Chromoting/GTMOAuth2ViewTouch.xib
new file mode 100644
index 0000000000..4f91fa4ad2
--- /dev/null
+++ b/remoting/ios/Chromoting/GTMOAuth2ViewTouch.xib
@@ -0,0 +1,494 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="7.10">
+ <data>
+ <int key="IBDocument.SystemTarget">1024</int>
+ <string key="IBDocument.SystemVersion">12C60</string>
+ <string key="IBDocument.InterfaceBuilderVersion">2840</string>
+ <string key="IBDocument.AppKitVersion">1187.34</string>
+ <string key="IBDocument.HIToolboxVersion">625.00</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string key="NS.object.0">1926</string>
+ </object>
+ <object class="NSArray" key="IBDocument.IntegratedClassDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>IBProxyObject</string>
+ <string>IBUIActivityIndicatorView</string>
+ <string>IBUIBarButtonItem</string>
+ <string>IBUIButton</string>
+ <string>IBUINavigationItem</string>
+ <string>IBUIView</string>
+ <string>IBUIWebView</string>
+ </object>
+ <object class="NSArray" key="IBDocument.PluginDependencies">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.Metadata">
+ <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+ <integer value="1" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBProxyObject" id="372490531">
+ <string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBProxyObject" id="975951072">
+ <string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUINavigationItem" id="1047805472">
+ <string key="IBUITitle">OAuth</string>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIBarButtonItem" id="961671599">
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIStyle">1</int>
+ </object>
+ <object class="IBUIView" id="808907889">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">292</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIButton" id="453250804">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrameSize">{30, 30}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="981703116"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">â—€</string>
+ <object class="NSColor" key="IBUIHighlightedTitleColor" id="193465259">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ </object>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41OTYwNzg0NiAwLjY4NjI3NDUzIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUINormalTitleShadowColor" id="999379443">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MC41AA</bytes>
+ </object>
+ <object class="IBUIFontDescription" key="IBUIFontDescription" id="621440819">
+ <string key="name">Helvetica-Bold</string>
+ <string key="family">Helvetica</string>
+ <int key="traits">2</int>
+ <double key="pointSize">24</double>
+ </object>
+ <object class="NSFont" key="IBUIFont" id="530402572">
+ <string key="NSName">Helvetica-Bold</string>
+ <double key="NSSize">24</double>
+ <int key="NSfFlags">16</int>
+ </object>
+ </object>
+ <object class="IBUIButton" id="981703116">
+ <reference key="NSNextResponder" ref="808907889"/>
+ <int key="NSvFlags">292</int>
+ <string key="NSFrame">{{30, 0}, {30, 30}}</string>
+ <reference key="NSSuperview" ref="808907889"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIContentHorizontalAlignment">0</int>
+ <int key="IBUIContentVerticalAlignment">0</int>
+ <string key="IBUITitleShadowOffset">{0, -2}</string>
+ <string key="IBUINormalTitle">â–¶</string>
+ <reference key="IBUIHighlightedTitleColor" ref="193465259"/>
+ <object class="NSColor" key="IBUIDisabledTitleColor">
+ <int key="NSColorSpace">2</int>
+ <bytes key="NSRGB">MC41ODQzMTM3NSAwLjY3NDUwOTgyIDAuOTUyOTQxMjQgMC42MDAwMDAwMgA</bytes>
+ </object>
+ <reference key="IBUINormalTitleColor" ref="193465259"/>
+ <reference key="IBUINormalTitleShadowColor" ref="999379443"/>
+ <reference key="IBUIFontDescription" ref="621440819"/>
+ <reference key="IBUIFont" ref="530402572"/>
+ </object>
+ </object>
+ <string key="NSFrameSize">{60, 30}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="453250804"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MSAwAA</bytes>
+ </object>
+ <bool key="IBUIOpaque">NO</bool>
+ <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
+ <object class="IBUISimulatedOrientationMetrics" key="IBUISimulatedOrientationMetrics">
+ <int key="IBUIInterfaceOrientation">3</int>
+ <int key="interfaceOrientation">3</int>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ <object class="IBUIView" id="426018584">
+ <reference key="NSNextResponder"/>
+ <int key="NSvFlags">274</int>
+ <object class="NSMutableArray" key="NSSubviews">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBUIWebView" id="663477729">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">274</int>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="268967673"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">1</int>
+ <bytes key="NSRGB">MSAxIDEAA</bytes>
+ </object>
+ <bool key="IBUIClipsSubviews">YES</bool>
+ <bool key="IBUIMultipleTouchEnabled">YES</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <int key="IBUIDataDetectorTypes">1</int>
+ <bool key="IBUIDetectsPhoneNumbers">YES</bool>
+ </object>
+ <object class="IBUIActivityIndicatorView" id="268967673">
+ <reference key="NSNextResponder" ref="426018584"/>
+ <int key="NSvFlags">301</int>
+ <string key="NSFrame">{{150, 115}, {20, 20}}</string>
+ <reference key="NSSuperview" ref="426018584"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView"/>
+ <string key="NSReuseIdentifierKey">_NS:9</string>
+ <bool key="IBUIOpaque">NO</bool>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <bool key="IBUIHidesWhenStopped">NO</bool>
+ <bool key="IBUIAnimating">YES</bool>
+ <int key="IBUIStyle">2</int>
+ </object>
+ </object>
+ <string key="NSFrameSize">{320, 460}</string>
+ <reference key="NSSuperview"/>
+ <reference key="NSWindow"/>
+ <reference key="NSNextKeyView" ref="663477729"/>
+ <object class="NSColor" key="IBUIBackgroundColor">
+ <int key="NSColorSpace">3</int>
+ <bytes key="NSWhite">MQA</bytes>
+ <object class="NSColorSpace" key="NSCustomColorSpace">
+ <int key="NSID">2</int>
+ </object>
+ </object>
+ <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ </object>
+ </object>
+ <object class="IBObjectContainer" key="IBDocument.Objects">
+ <object class="NSMutableArray" key="connectionRecords">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">20</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">navButtonsView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="808907889"/>
+ </object>
+ <int key="connectionID">22</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">backButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="453250804"/>
+ </object>
+ <int key="connectionID">25</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">forwardButton</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="981703116"/>
+ </object>
+ <int key="connectionID">26</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">view</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="426018584"/>
+ </object>
+ <int key="connectionID">28</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">webView</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="663477729"/>
+ </object>
+ <int key="connectionID">29</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">initialActivityIndicator</string>
+ <reference key="source" ref="372490531"/>
+ <reference key="destination" ref="268967673"/>
+ </object>
+ <int key="connectionID">33</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">delegate</string>
+ <reference key="source" ref="663477729"/>
+ <reference key="destination" ref="372490531"/>
+ </object>
+ <int key="connectionID">9</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchOutletConnection" key="connection">
+ <string key="label">rightBarButtonItem</string>
+ <reference key="source" ref="1047805472"/>
+ <reference key="destination" ref="961671599"/>
+ </object>
+ <int key="connectionID">14</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goBack</string>
+ <reference key="source" ref="453250804"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">18</int>
+ </object>
+ <object class="IBConnectionRecord">
+ <object class="IBCocoaTouchEventConnection" key="connection">
+ <string key="label">goForward</string>
+ <reference key="source" ref="981703116"/>
+ <reference key="destination" ref="663477729"/>
+ <int key="IBEventType">7</int>
+ </object>
+ <int key="connectionID">19</int>
+ </object>
+ </object>
+ <object class="IBMutableOrderedSet" key="objectRecords">
+ <object class="NSArray" key="orderedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBObjectRecord">
+ <int key="objectID">0</int>
+ <object class="NSArray" key="object" id="0">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="children" ref="1000"/>
+ <nil key="parent"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-1</int>
+ <reference key="object" ref="372490531"/>
+ <reference key="parent" ref="0"/>
+ <string key="objectName">File's Owner</string>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">-2</int>
+ <reference key="object" ref="975951072"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">6</int>
+ <reference key="object" ref="1047805472"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">10</int>
+ <reference key="object" ref="961671599"/>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">15</int>
+ <reference key="object" ref="808907889"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="453250804"/>
+ <reference ref="981703116"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">16</int>
+ <reference key="object" ref="453250804"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">17</int>
+ <reference key="object" ref="981703116"/>
+ <reference key="parent" ref="808907889"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">27</int>
+ <reference key="object" ref="426018584"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="663477729"/>
+ <reference ref="268967673"/>
+ </object>
+ <reference key="parent" ref="0"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">4</int>
+ <reference key="object" ref="663477729"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">31</int>
+ <reference key="object" ref="268967673"/>
+ <reference key="parent" ref="426018584"/>
+ </object>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="flattenedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>-1.CustomClassName</string>
+ <string>-1.IBPluginDependency</string>
+ <string>-2.CustomClassName</string>
+ <string>-2.IBPluginDependency</string>
+ <string>10.IBPluginDependency</string>
+ <string>15.IBPluginDependency</string>
+ <string>16.IBPluginDependency</string>
+ <string>17.IBPluginDependency</string>
+ <string>27.IBPluginDependency</string>
+ <string>31.IBPluginDependency</string>
+ <string>4.IBPluginDependency</string>
+ <string>6.IBPluginDependency</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>GTMOAuth2ViewControllerTouch</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>UIResponder</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="unlocalizedProperties">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="activeLocalization"/>
+ <object class="NSMutableDictionary" key="localizations">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference key="dict.sortedKeys" ref="0"/>
+ <reference key="dict.values" ref="0"/>
+ </object>
+ <nil key="sourceID"/>
+ <int key="maxID">33</int>
+ </object>
+ <object class="IBClassDescriber" key="IBDocument.Classes">
+ <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBPartialClassDescription">
+ <string key="className">GTMOAuth2ViewControllerTouch</string>
+ <string key="superclassName">UIViewController</string>
+ <object class="NSMutableDictionary" key="outlets">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>UIButton</string>
+ <string>UIButton</string>
+ <string>UIActivityIndicatorView</string>
+ <string>UIView</string>
+ <string>UIBarButtonItem</string>
+ <string>UIWebView</string>
+ </object>
+ </object>
+ <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSArray" key="dict.sortedKeys">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>backButton</string>
+ <string>forwardButton</string>
+ <string>initialActivityIndicator</string>
+ <string>navButtonsView</string>
+ <string>rightBarButtonItem</string>
+ <string>webView</string>
+ </object>
+ <object class="NSArray" key="dict.values">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="IBToOneOutletInfo">
+ <string key="name">backButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">forwardButton</string>
+ <string key="candidateClassName">UIButton</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">initialActivityIndicator</string>
+ <string key="candidateClassName">UIActivityIndicatorView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">navButtonsView</string>
+ <string key="candidateClassName">UIView</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">rightBarButtonItem</string>
+ <string key="candidateClassName">UIBarButtonItem</string>
+ </object>
+ <object class="IBToOneOutletInfo">
+ <string key="name">webView</string>
+ <string key="candidateClassName">UIWebView</string>
+ </object>
+ </object>
+ </object>
+ <object class="IBClassDescriptionSource" key="sourceIdentifier">
+ <string key="majorKey">IBProjectSource</string>
+ <string key="minorKey">./Classes/GTMOAuth2ViewControllerTouch.h</string>
+ </object>
+ </object>
+ </object>
+ </object>
+ <int key="IBDocument.localizationMode">0</int>
+ <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1024" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS</string>
+ <real value="1536" key="NS.object.0"/>
+ </object>
+ <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
+ <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3</string>
+ <integer value="3000" key="NS.object.0"/>
+ </object>
+ <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+ <int key="IBDocument.defaultPropertyAccessControl">3</int>
+ <string key="IBCocoaTouchPluginVersion">1926</string>
+ </data>
+</archive>
diff --git a/remoting/ios/Chromoting/Images.xcassets/AppIcon.appiconset/Contents.json b/remoting/ios/Chromoting/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..a2818410b9
--- /dev/null
+++ b/remoting/ios/Chromoting/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,91 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "57x57",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "57x57",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "chromoting120.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "50x50",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "50x50",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "72x72",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "72x72",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "chromoting76.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "chromoting152.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/remoting/ios/Chromoting/Images.xcassets/LaunchImage.launchimage/Contents.json b/remoting/ios/Chromoting/Images.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000000..a0ad363c85
--- /dev/null
+++ b/remoting/ios/Chromoting/Images.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,36 @@
+{
+ "images" : [
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/remoting/ios/Chromoting/en.lproj/InfoPlist.strings b/remoting/ios/Chromoting/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..653031a96c
--- /dev/null
+++ b/remoting/ios/Chromoting/en.lproj/InfoPlist.strings
@@ -0,0 +1,3 @@
+/* This file is required */
+/* Localized versions of Info.plist keys */
+
diff --git a/remoting/ios/Chromoting/main.mm b/remoting/ios/Chromoting/main.mm
new file mode 100644
index 0000000000..cd068167b2
--- /dev/null
+++ b/remoting/ios/Chromoting/main.mm
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import <UIKit/UIKit.h>
+
+#include "base/at_exit.h"
+#include "base/command_line.h"
+#include "media/base/yuv_convert.h"
+#include "net/socket/ssl_server_socket.h"
+
+#import "app_delegate.h"
+
+int main(int argc, char* argv[]) {
+ // This class is designed to fulfill the dependents needs when it goes out of
+ // scope and gets destructed
+ base::AtExitManager exitManager;
+
+ // Publicize the CommandLine
+ CommandLine::Init(argc, argv);
+
+#ifdef DEBUG
+ // Set min log level for debug builds. For some reason this has to be
+ // negative.
+ logging::SetMinLogLevel(-1);
+#endif
+
+ // Allows later decoding of video frames.
+ media::InitializeCPUSpecificYUVConversions();
+
+ // Enable support for SSL server sockets, which must be done as early as
+ // possible, preferably before any NSS SSL sockets (client or server) have
+ // been created.
+ net::EnableSSLServerSockets();
+
+ @autoreleasepool {
+ return UIApplicationMain(
+ argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+}
diff --git a/remoting/ios/Chromoting_unittests/Chromoting_unittests-Info.plist b/remoting/ios/Chromoting_unittests/Chromoting_unittests-Info.plist
new file mode 100644
index 0000000000..e48cdc6231
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/Chromoting_unittests-Info.plist
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleExecutable</key>
+ <string>${EXECUTABLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>net.fusionlabs.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>2.4</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UIStatusBarHidden</key>
+ <false/>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/.xccurrentversion b/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/.xccurrentversion
new file mode 100644
index 0000000000..855b6eecca
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>_XCCurrentVersionName</key>
+ <string>Chromoting_unittests.xcdatamodel</string>
+</dict>
+</plist>
diff --git a/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/Chromoting_unittests.xcdatamodel/contents b/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/Chromoting_unittests.xcdatamodel/contents
new file mode 100644
index 0000000000..193f33c9c4
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/Chromoting_unittests.xcdatamodeld/Chromoting_unittests.xcdatamodel/contents
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model name="Test1.xcdatamodel" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1" systemVersion="11A491" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
+ <elements/>
+</model> \ No newline at end of file
diff --git a/remoting/ios/Chromoting_unittests/Images.xcassets/AppIcon.appiconset/Contents.json b/remoting/ios/Chromoting_unittests/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..91bf9c14a7
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,53 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/remoting/ios/Chromoting_unittests/Images.xcassets/LaunchImage.launchimage/Contents.json b/remoting/ios/Chromoting_unittests/Images.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 0000000000..6f870a4629
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/Images.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,51 @@
+{
+ "images" : [
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/remoting/ios/Chromoting_unittests/en.lproj/InfoPlist.strings b/remoting/ios/Chromoting_unittests/en.lproj/InfoPlist.strings
new file mode 100644
index 0000000000..477b28ff8f
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/en.lproj/InfoPlist.strings
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/remoting/ios/Chromoting_unittests/main.mm b/remoting/ios/Chromoting_unittests/main.mm
new file mode 100644
index 0000000000..3efc1cdb24
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/main.mm
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import <UIKit/UIKit.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/test/test_suite.h"
+#include "testing/gtest_mac.h"
+
+#import "remoting/ios/Chromoting_unittests/main_no_arc.h"
+
+int main(int argc, char* argv[]) {
+ // Initialization that must occur with no Automatic Reference Counting (ARC)
+ remoting::main_no_arc::init();
+
+ testing::InitGoogleTest(&argc, argv);
+ scoped_ptr<base::TestSuite> runner(new base::TestSuite(argc, argv));
+ runner.get()->Run();
+}
diff --git a/remoting/ios/Chromoting_unittests/main_no_arc.cc b/remoting/ios/Chromoting_unittests/main_no_arc.cc
new file mode 100644
index 0000000000..2bfbd872b6
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/main_no_arc.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/ios/Chromoting_unittests/main_no_arc.h"
+
+#include "base/message_loop/message_loop.h"
+
+namespace remoting {
+namespace main_no_arc {
+
+void init() {
+ // Declare the pump factory before the test suite can declare it. The test
+ // suite assumed we are running in x86 simulator, but this test project runs
+ // on an actual device
+ base::MessageLoop::InitMessagePumpForUIFactory(&base::MessagePumpMac::Create);
+}
+
+} // main_no_arc
+} // remoting
diff --git a/remoting/ios/Chromoting_unittests/main_no_arc.h b/remoting/ios/Chromoting_unittests/main_no_arc.h
new file mode 100644
index 0000000000..1d97761ce1
--- /dev/null
+++ b/remoting/ios/Chromoting_unittests/main_no_arc.h
@@ -0,0 +1,11 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+namespace remoting {
+namespace main_no_arc {
+
+void init();
+
+} // main_no_arc
+} // remoting
diff --git a/remoting/ios/DEPS b/remoting/ios/DEPS
new file mode 100644
index 0000000000..c3c25f4764
--- /dev/null
+++ b/remoting/ios/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+remoting/host",
+]
diff --git a/remoting/ios/app_delegate.h b/remoting/ios/app_delegate.h
new file mode 100644
index 0000000000..bfd60ed6be
--- /dev/null
+++ b/remoting/ios/app_delegate.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_APP_DELEGATE_H_
+#define REMOTING_IOS_APP_DELEGATE_H_
+
+#import <UIKit/UIKit.h>
+
+// Default created delegate class for the entire application
+@interface AppDelegate : UIResponder<UIApplicationDelegate>
+
+@property(strong, nonatomic) UIWindow* window;
+
+@end
+
+#endif // REMOTING_IOS_APP_DELEGATE_H_ \ No newline at end of file
diff --git a/remoting/ios/app_delegate.mm b/remoting/ios/app_delegate.mm
new file mode 100644
index 0000000000..7b8fc36638
--- /dev/null
+++ b/remoting/ios/app_delegate.mm
@@ -0,0 +1,18 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/app_delegate.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication*)application
+ didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
+ return YES;
+}
+
+@end
diff --git a/remoting/ios/authorize.h b/remoting/ios/authorize.h
new file mode 100644
index 0000000000..1b3e0eab65
--- /dev/null
+++ b/remoting/ios/authorize.h
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_AUTHORIZE_H_
+#define REMOTING_IOS_AUTHORIZE_H_
+
+#import <UIKit/UIKit.h>
+
+// TODO (aboone) This include is for The Google Toolbox for Mac OAuth 2
+// https://code.google.com/p/gtm-oauth2/ This may need to be added as a
+// third-party or locate the proper project in Chromium.
+#import "GTMOAuth2Authentication.h"
+
+@interface Authorize : NSObject
+
++ (GTMOAuth2Authentication*)getAnyExistingAuthorization;
+
++ (void)beginRequest:(GTMOAuth2Authentication*)authorization
+ delegate:self
+ didFinishSelector:(SEL)sel;
+
++ (void)appendCredentials:(NSMutableURLRequest*)request;
+
++ (UINavigationController*)createLoginController:(id)delegate
+ finishedSelector:(SEL)finishedSelector;
+
+@end
+
+#endif // REMOTING_IOS_AUTHORIZE_H_
diff --git a/remoting/ios/authorize.mm b/remoting/ios/authorize.mm
new file mode 100644
index 0000000000..92f467c648
--- /dev/null
+++ b/remoting/ios/authorize.mm
@@ -0,0 +1,123 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/authorize.h"
+
+// TODO (aboone) This include is for The Google Toolbox for Mac OAuth 2
+// Controllers https://code.google.com/p/gtm-oauth2/ This may need to be added
+// as a third-party or locate the proper project in Chromium.
+#import "GTMOAuth2ViewControllerTouch.h"
+
+#include "google_apis/google_api_keys.h"
+// TODO (aboone) Pulling in some service values from the host side. The cc's
+// are also compiled as part of this project because the target remoting_host
+// does not build on iOS right now.
+#include "remoting/host/service_urls.h"
+#include "remoting/host/setup/oauth_helper.h"
+
+namespace {
+static NSString* const kKeychainItemName = @"Google Chromoting iOS";
+
+NSString* ClientId() {
+ return
+ [NSString stringWithUTF8String:google_apis::GetOAuth2ClientID(
+ google_apis::CLIENT_REMOTING).c_str()];
+}
+
+NSString* ClientSecret() {
+ return
+ [NSString stringWithUTF8String:google_apis::GetOAuth2ClientSecret(
+ google_apis::CLIENT_REMOTING).c_str()];
+}
+
+NSString* Scopes() {
+ return [NSString stringWithUTF8String:remoting::GetOauthScope().c_str()];
+}
+
+NSMutableString* HostURL() {
+ return
+ [NSMutableString stringWithUTF8String:remoting::ServiceUrls::GetInstance()
+ ->directory_hosts_url()
+ .c_str()];
+}
+
+NSString* APIKey() {
+ return [NSString stringWithUTF8String:google_apis::GetAPIKey().c_str()];
+}
+
+} // namespace
+
+@implementation Authorize
+
++ (GTMOAuth2Authentication*)getAnyExistingAuthorization {
+ // Ensure the google_apis lib has keys
+ // If this check fails then google_apis was not built right
+ // TODO (aboone) For now we specify the preprocessor macros for
+ // GOOGLE_CLIENT_SECRET_REMOTING and GOOGLE_CLIENT_ID_REMOTING when building
+ // the google_apis target. The values may be developer specific, and should
+ // be well know to the project staff.
+ // See http://www.chromium.org/developers/how-tos/api-keys for more general
+ // information.
+ DCHECK(![ClientId() isEqualToString:@"dummytoken"]);
+
+ return [GTMOAuth2ViewControllerTouch
+ authForGoogleFromKeychainForName:kKeychainItemName
+ clientID:ClientId()
+ clientSecret:ClientSecret()];
+}
+
++ (void)beginRequest:(GTMOAuth2Authentication*)authReq
+ delegate:(id)delegate
+ didFinishSelector:(SEL)sel {
+ // Build request URL using API HTTP endpoint, and our api key
+ NSMutableString* hostsUrl = HostURL();
+ [hostsUrl appendString:@"?key="];
+ [hostsUrl appendString:APIKey()];
+
+ NSMutableURLRequest* theRequest =
+ [NSMutableURLRequest requestWithURL:[NSURL URLWithString:hostsUrl]];
+
+ // Add scopes if needed
+ NSString* scope = authReq.scope;
+
+ if ([scope rangeOfString:Scopes()].location == NSNotFound) {
+ scope = [GTMOAuth2Authentication scopeWithStrings:scope, Scopes(), nil];
+ authReq.scope = scope;
+ }
+
+ // Execute request async
+ [authReq authorizeRequest:theRequest delegate:delegate didFinishSelector:sel];
+}
+
++ (void)appendCredentials:(NSMutableURLRequest*)request {
+ // Add credentials for service
+ [request addValue:ClientId() forHTTPHeaderField:@"client_id"];
+ [request addValue:ClientSecret() forHTTPHeaderField:@"client_secret"];
+}
+
++ (UINavigationController*)createLoginController:(id)delegate
+ finishedSelector:(SEL)finishedSelector {
+ [GTMOAuth2ViewControllerTouch
+ removeAuthFromKeychainForName:kKeychainItemName];
+
+ // When the sign in is complete a http redirection occurs, and the
+ // user would see the output. We do not want the user to notice this
+ // transition. Wrapping the oAuth2 Controller in a
+ // UINavigationController causes the view to render as a blank/black
+ // page when a http redirection occurs.
+ return [[UINavigationController alloc]
+ initWithRootViewController:[[GTMOAuth2ViewControllerTouch alloc]
+ initWithScope:Scopes()
+ clientID:ClientId()
+ clientSecret:ClientSecret()
+ keychainItemName:kKeychainItemName
+ delegate:delegate
+ finishedSelector:finishedSelector]];
+}
+
+@end
diff --git a/remoting/ios/bridge/DEPS b/remoting/ios/bridge/DEPS
new file mode 100644
index 0000000000..565acfcec6
--- /dev/null
+++ b/remoting/ios/bridge/DEPS
@@ -0,0 +1,8 @@
+include_rules = [
+ "+net/url_request",
+
+ "-remoting/host",
+ "+remoting/client",
+ "+remoting/jingle_glue",
+ "+remoting/protocol",
+]
diff --git a/remoting/ios/bridge/client_instance.cc b/remoting/ios/bridge/client_instance.cc
new file mode 100644
index 0000000000..6066f950ad
--- /dev/null
+++ b/remoting/ios/bridge/client_instance.cc
@@ -0,0 +1,397 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/ios/bridge/client_instance.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/synchronization/waitable_event.h"
+#include "net/socket/client_socket_factory.h"
+#include "remoting/base/url_request_context.h"
+#include "remoting/client/audio_player.h"
+#include "remoting/client/plugin/delegating_signal_strategy.h"
+#include "remoting/ios/bridge/client_proxy.h"
+#include "remoting/jingle_glue/chromium_port_allocator.h"
+#include "remoting/protocol/host_stub.h"
+#include "remoting/protocol/libjingle_transport_factory.h"
+
+namespace {
+const char* const kXmppServer = "talk.google.com";
+const int kXmppPort = 5222;
+const bool kXmppUseTls = true;
+
+void DoNothing() {}
+} // namespace
+
+namespace remoting {
+
+ClientInstance::ClientInstance(const base::WeakPtr<ClientProxy>& proxy,
+ const std::string& username,
+ const std::string& auth_token,
+ const std::string& host_jid,
+ const std::string& host_id,
+ const std::string& host_pubkey,
+ const std::string& pairing_id,
+ const std::string& pairing_secret)
+ : proxyToClient_(proxy), host_id_(host_id), create_pairing_(false) {
+
+ if (!base::MessageLoop::current()) {
+ VLOG(1) << "Starting main message loop";
+ ui_loop_ = new base::MessageLoopForUI();
+ ui_loop_->Attach();
+ } else {
+ VLOG(1) << "Using existing main message loop";
+ ui_loop_ = base::MessageLoopForUI::current();
+ }
+
+ VLOG(1) << "Spawning additional threads";
+
+ // |ui_loop_| runs on the main thread, so |ui_task_runner_| will run on the
+ // main thread. We can not kill the main thread when the message loop becomes
+ // idle so the callback function does nothing (as opposed to the typical
+ // base::MessageLoop::QuitClosure())
+ ui_task_runner_ = new AutoThreadTaskRunner(ui_loop_->message_loop_proxy(),
+ base::Bind(&::DoNothing));
+
+ network_task_runner_ = AutoThread::CreateWithType(
+ "native_net", ui_task_runner_, base::MessageLoop::TYPE_IO);
+
+ url_requester_ = new URLRequestContextGetter(network_task_runner_);
+
+ client_context_.reset(new ClientContext(network_task_runner_));
+
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ // Initialize XMPP config.
+ xmpp_config_.host = kXmppServer;
+ xmpp_config_.port = kXmppPort;
+ xmpp_config_.use_tls = kXmppUseTls;
+ xmpp_config_.username = username;
+ xmpp_config_.auth_token = auth_token;
+ xmpp_config_.auth_service = "oauth2";
+
+ // Initialize ClientConfig.
+ client_config_.host_jid = host_jid;
+ client_config_.host_public_key = host_pubkey;
+ client_config_.authentication_tag = host_id_;
+ client_config_.client_pairing_id = pairing_id;
+ client_config_.client_paired_secret = pairing_secret;
+ client_config_.authentication_methods.push_back(
+ protocol::AuthenticationMethod::FromString("spake2_pair"));
+ client_config_.authentication_methods.push_back(
+ protocol::AuthenticationMethod::FromString("spake2_hmac"));
+ client_config_.authentication_methods.push_back(
+ protocol::AuthenticationMethod::FromString("spake2_plain"));
+}
+
+ClientInstance::~ClientInstance() {}
+
+void ClientInstance::Start() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ // Creates a reference to |this|, so don't want to bind during constructor
+ client_config_.fetch_secret_callback =
+ base::Bind(&ClientInstance::FetchSecret, this);
+
+ view_.reset(new FrameConsumerBridge(
+ base::Bind(&ClientProxy::RedrawCanvas, proxyToClient_)));
+
+ // |consumer_proxy| must be created on the UI thread to proxy calls from the
+ // network or decode thread to the UI thread, but ownership will belong to a
+ // SoftwareVideoRenderer which runs on the network thread.
+ scoped_refptr<FrameConsumerProxy> consumer_proxy =
+ new FrameConsumerProxy(ui_task_runner_, view_->AsWeakPtr());
+
+ // Post a task to start connection
+ base::WaitableEvent done_event(true, false);
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::ConnectToHostOnNetworkThread,
+ this,
+ consumer_proxy,
+ base::Bind(&base::WaitableEvent::Signal,
+ base::Unretained(&done_event))));
+ // Wait until initialization completes before continuing
+ done_event.Wait();
+}
+
+void ClientInstance::Cleanup() {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+
+ client_config_.fetch_secret_callback.Reset(); // Release ref to this
+ // |view_| must be destroyed on the UI thread before the producer is gone.
+ view_.reset();
+
+ base::WaitableEvent done_event(true, false);
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::DisconnectFromHostOnNetworkThread,
+ this,
+ base::Bind(&base::WaitableEvent::Signal,
+ base::Unretained(&done_event))));
+ // Wait until we are fully disconnected before continuing
+ done_event.Wait();
+}
+
+// HOST attempts to continue automatically with previously supplied credentials,
+// if it can't it requests the user's PIN.
+void ClientInstance::FetchSecret(
+ bool pairable,
+ const protocol::SecretFetchedCallback& callback) {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::FetchSecret, this, pairable, callback));
+ return;
+ }
+
+ pin_callback_ = callback;
+
+ if (proxyToClient_) {
+ if (!client_config_.client_pairing_id.empty()) {
+ // We attempted to connect using an existing pairing that was rejected.
+ // Unless we forget about the stale credentials, we'll continue trying
+ // them.
+ VLOG(1) << "Deleting rejected pairing credentials";
+
+ proxyToClient_->CommitPairingCredentials(host_id_, "", "");
+ }
+ proxyToClient_->DisplayAuthenticationPrompt(pairable);
+ }
+}
+
+void ClientInstance::ProvideSecret(const std::string& pin,
+ bool create_pairing) {
+ DCHECK(ui_task_runner_->BelongsToCurrentThread());
+ create_pairing_ = create_pairing;
+
+ // Before this function can complete, FetchSecret must be called
+ DCHECK(!pin_callback_.is_null());
+ network_task_runner_->PostTask(FROM_HERE, base::Bind(pin_callback_, pin));
+}
+
+void ClientInstance::PerformMouseAction(
+ const webrtc::DesktopVector& position,
+ const webrtc::DesktopVector& wheel_delta,
+ int /* protocol::MouseEvent_MouseButton */ whichButton,
+ bool button_down) {
+ if (!network_task_runner_->BelongsToCurrentThread()) {
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::PerformMouseAction,
+ this,
+ position,
+ wheel_delta,
+ whichButton,
+ button_down));
+ return;
+ }
+
+ protocol::MouseEvent_MouseButton mButton;
+
+ // Button must be within the bounds of the MouseEvent_MouseButton enum.
+ switch (whichButton) {
+ case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_LEFT:
+ mButton =
+ protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_LEFT;
+ break;
+ case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MAX:
+ mButton =
+ protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MAX;
+ break;
+ case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_MIDDLE:
+ mButton = protocol::MouseEvent_MouseButton::
+ MouseEvent_MouseButton_BUTTON_MIDDLE;
+ break;
+ case protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_RIGHT:
+ mButton =
+ protocol::MouseEvent_MouseButton::MouseEvent_MouseButton_BUTTON_RIGHT;
+ break;
+ case protocol::MouseEvent_MouseButton::
+ MouseEvent_MouseButton_BUTTON_UNDEFINED:
+ mButton = protocol::MouseEvent_MouseButton::
+ MouseEvent_MouseButton_BUTTON_UNDEFINED;
+ break;
+ default:
+ LOG(FATAL) << "Invalid constant for MouseEvent_MouseButton";
+ mButton = protocol::MouseEvent_MouseButton::
+ MouseEvent_MouseButton_BUTTON_UNDEFINED;
+ break;
+ }
+
+ protocol::MouseEvent action;
+ action.set_x(position.x());
+ action.set_y(position.y());
+ action.set_wheel_delta_x(wheel_delta.x());
+ action.set_wheel_delta_y(wheel_delta.y());
+ action.set_button(mButton);
+ if (mButton != protocol::MouseEvent::BUTTON_UNDEFINED)
+ action.set_button_down(button_down);
+
+ connection_->input_stub()->InjectMouseEvent(action);
+}
+
+void ClientInstance::PerformKeyboardAction(int key_code, bool key_down) {
+ if (!network_task_runner_->BelongsToCurrentThread()) {
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ClientInstance::PerformKeyboardAction, this, key_code, key_down));
+ return;
+ }
+
+ protocol::KeyEvent action;
+ action.set_usb_keycode(key_code);
+ action.set_pressed(key_down);
+ connection_->input_stub()->InjectKeyEvent(action);
+}
+
+void ClientInstance::OnConnectionState(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::OnConnectionState, this, state, error));
+ return;
+ }
+
+ // TODO (aboone) This functionality is not scheduled for QA yet.
+ // if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
+ // VLOG(1) << "Attempting to pair with host";
+ // protocol::PairingRequest request;
+ // request.set_client_name("iOS");
+ // connection_->host_stub()->RequestPairing(request);
+ // }
+
+ if (proxyToClient_)
+ proxyToClient_->ReportConnectionStatus(state, error);
+}
+
+void ClientInstance::OnConnectionReady(bool ready) {
+ // We ignore this message, since OnConnectionState tells us the same thing.
+}
+
+void ClientInstance::OnRouteChanged(const std::string& channel_name,
+ const protocol::TransportRoute& route) {
+ VLOG(1) << "Using " << protocol::TransportRoute::GetTypeString(route.type)
+ << " connection for " << channel_name << " channel";
+}
+
+void ClientInstance::SetCapabilities(const std::string& capabilities) {
+ DCHECK(video_renderer_);
+ DCHECK(connection_);
+ DCHECK(connection_->state() == protocol::ConnectionToHost::CONNECTED);
+ video_renderer_->Initialize(connection_->config());
+}
+
+void ClientInstance::SetPairingResponse(
+ const protocol::PairingResponse& response) {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ClientInstance::SetPairingResponse, this, response));
+ return;
+ }
+
+ VLOG(1) << "Successfully established pairing with host";
+
+ if (proxyToClient_)
+ proxyToClient_->CommitPairingCredentials(
+ host_id_, response.client_id(), response.shared_secret());
+}
+
+void ClientInstance::DeliverHostMessage(
+ const protocol::ExtensionMessage& message) {
+ NOTIMPLEMENTED();
+}
+
+// Returning interface of protocol::ClipboardStub
+protocol::ClipboardStub* ClientInstance::GetClipboardStub() { return this; }
+
+// Returning interface of protocol::CursorShapeStub
+protocol::CursorShapeStub* ClientInstance::GetCursorShapeStub() { return this; }
+
+scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
+ClientInstance::GetTokenFetcher(const std::string& host_public_key) {
+ // Returns null when third-party authentication is unsupported.
+ return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>();
+}
+
+void ClientInstance::InjectClipboardEvent(
+ const protocol::ClipboardEvent& event) {
+ NOTIMPLEMENTED();
+}
+
+void ClientInstance::SetCursorShape(const protocol::CursorShapeInfo& shape) {
+ if (!ui_task_runner_->BelongsToCurrentThread()) {
+ ui_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&ClientInstance::SetCursorShape, this, shape));
+ return;
+ }
+ if (proxyToClient_)
+ proxyToClient_->UpdateCursorShape(shape);
+}
+
+void ClientInstance::ConnectToHostOnNetworkThread(
+ scoped_refptr<FrameConsumerProxy> consumer_proxy,
+ const base::Closure& done) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ client_context_->Start();
+
+ video_renderer_.reset(
+ new SoftwareVideoRenderer(client_context_->main_task_runner(),
+ client_context_->decode_task_runner(),
+ consumer_proxy));
+
+ view_->Initialize(video_renderer_.get());
+
+ connection_.reset(new protocol::ConnectionToHost(true));
+
+ client_.reset(new ChromotingClient(client_config_,
+ client_context_.get(),
+ connection_.get(),
+ this,
+ video_renderer_.get(),
+ scoped_ptr<AudioPlayer>()));
+
+ signaling_.reset(
+ new XmppSignalStrategy(net::ClientSocketFactory::GetDefaultFactory(),
+ url_requester_,
+ xmpp_config_));
+
+ NetworkSettings network_settings(NetworkSettings::NAT_TRAVERSAL_ENABLED);
+
+ scoped_ptr<ChromiumPortAllocator> port_allocator(
+ ChromiumPortAllocator::Create(url_requester_, network_settings));
+
+ scoped_ptr<protocol::TransportFactory> transport_factory(
+ new protocol::LibjingleTransportFactory(
+ signaling_.get(),
+ port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
+ network_settings));
+
+ client_->Start(signaling_.get(), transport_factory.Pass());
+
+ if (!done.is_null())
+ done.Run();
+}
+
+void ClientInstance::DisconnectFromHostOnNetworkThread(
+ const base::Closure& done) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ host_id_.clear();
+
+ // |client_| must be torn down before |signaling_|.
+ connection_.reset();
+ client_.reset();
+ signaling_.reset();
+ video_renderer_.reset();
+ client_context_->Stop();
+ if (!done.is_null())
+ done.Run();
+}
+
+} // namespace remoting
diff --git a/remoting/ios/bridge/client_instance.h b/remoting/ios/bridge/client_instance.h
new file mode 100644
index 0000000000..18f1cefaec
--- /dev/null
+++ b/remoting/ios/bridge/client_instance.h
@@ -0,0 +1,164 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_CLIENT_INSTANCE_H_
+#define REMOTING_IOS_BRIDGE_CLIENT_INSTANCE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "remoting/base/auto_thread.h"
+#include "remoting/client/chromoting_client.h"
+#include "remoting/client/client_config.h"
+#include "remoting/client/client_context.h"
+#include "remoting/client/client_user_interface.h"
+#include "remoting/client/frame_consumer_proxy.h"
+#include "remoting/client/software_video_renderer.h"
+
+#include "remoting/ios/bridge/frame_consumer_bridge.h"
+
+#include "remoting/jingle_glue/network_settings.h"
+#include "remoting/jingle_glue/xmpp_signal_strategy.h"
+#include "remoting/protocol/clipboard_stub.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "remoting/protocol/cursor_shape_stub.h"
+
+namespace remoting {
+
+class ClientProxy;
+
+// ClientUserInterface that indirectly makes and receives OBJ_C calls from the
+// UI application
+class ClientInstance : public ClientUserInterface,
+ public protocol::ClipboardStub,
+ public protocol::CursorShapeStub,
+ public base::RefCountedThreadSafe<ClientInstance> {
+ public:
+ // Initiates a connection with the specified host. Call from the UI thread. To
+ // connect with an unpaired host, pass in |pairing_id| and |pairing_secret| as
+ // empty strings.
+ ClientInstance(const base::WeakPtr<ClientProxy>& proxy,
+ const std::string& username,
+ const std::string& auth_token,
+ const std::string& host_jid,
+ const std::string& host_id,
+ const std::string& host_pubkey,
+ const std::string& pairing_id,
+ const std::string& pairing_secret);
+
+ // Begins the connection process. Should not be called again until after
+ // |CleanUp|
+ void Start();
+
+ // Terminates the current connection (if it hasn't already failed) and cleans
+ // up. Must be called before destruction can occur or a memory leak may occur.
+ void Cleanup();
+
+ // Notifies the user interface that the user needs to enter a PIN. The
+ // current authentication attempt is put on hold until |callback| is invoked.
+ // May be called on any thread.
+ void FetchSecret(bool pairable,
+ const protocol::SecretFetchedCallback& callback);
+
+ // Provides the user's PIN and resumes the host authentication attempt. Call
+ // on the UI thread once the user has finished entering this PIN into the UI,
+ // but only after the UI has been asked to provide a PIN (via FetchSecret()).
+ void ProvideSecret(const std::string& pin, bool create_pair);
+
+ // Moves the host's cursor to the specified coordinates, optionally with some
+ // mouse button depressed. If |button| is BUTTON_UNDEFINED, no click is made.
+ void PerformMouseAction(
+ const webrtc::DesktopVector& position,
+ const webrtc::DesktopVector& wheel_delta,
+ int /* protocol::MouseEvent_MouseButton */ whichButton,
+ bool button_down);
+
+ // Sends the provided keyboard scan code to the host.
+ void PerformKeyboardAction(int key_code, bool key_down);
+
+ // ClientUserInterface implementation.
+ virtual void OnConnectionState(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) OVERRIDE;
+ virtual void OnConnectionReady(bool ready) OVERRIDE;
+ virtual void OnRouteChanged(const std::string& channel_name,
+ const protocol::TransportRoute& route) OVERRIDE;
+ virtual void SetCapabilities(const std::string& capabilities) OVERRIDE;
+ virtual void SetPairingResponse(const protocol::PairingResponse& response)
+ OVERRIDE;
+ virtual void DeliverHostMessage(const protocol::ExtensionMessage& message)
+ OVERRIDE;
+ virtual protocol::ClipboardStub* GetClipboardStub() OVERRIDE;
+ virtual protocol::CursorShapeStub* GetCursorShapeStub() OVERRIDE;
+ virtual scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
+ GetTokenFetcher(const std::string& host_public_key) OVERRIDE;
+
+ // CursorShapeStub implementation.
+ virtual void InjectClipboardEvent(const protocol::ClipboardEvent& event)
+ OVERRIDE;
+
+ // ClipboardStub implementation.
+ virtual void SetCursorShape(const protocol::CursorShapeInfo& shape) OVERRIDE;
+
+ private:
+ // This object is ref-counted, so it cleans itself up.
+ virtual ~ClientInstance();
+
+ void ConnectToHostOnNetworkThread(
+ scoped_refptr<FrameConsumerProxy> consumer_proxy,
+ const base::Closure& done);
+ void DisconnectFromHostOnNetworkThread(const base::Closure& done);
+
+ // Proxy to exchange messages between the
+ // common Chromoting protocol and UI Application.
+ base::WeakPtr<ClientProxy> proxyToClient_;
+
+ // ID of the host we are connecting to.
+ std::string host_id_;
+
+ // This group of variables is to be used on the display thread.
+ scoped_ptr<SoftwareVideoRenderer> video_renderer_;
+ scoped_ptr<FrameConsumerBridge> view_;
+
+ // This group of variables is to be used on the network thread.
+ ClientConfig client_config_;
+ scoped_ptr<ClientContext> client_context_;
+ scoped_ptr<protocol::ConnectionToHost> connection_;
+ scoped_ptr<ChromotingClient> client_;
+ XmppSignalStrategy::XmppServerConfig xmpp_config_;
+ scoped_ptr<XmppSignalStrategy> signaling_; // Must outlive client_
+
+ // Pass this the user's PIN once we have it. To be assigned and accessed on
+ // the UI thread, but must be posted to the network thread to call it.
+ protocol::SecretFetchedCallback pin_callback_;
+
+ // Indicates whether to establish a new pairing with this host. This is
+ // modified in ProvideSecret(), but thereafter to be used only from the
+ // network thread. (This is safe because ProvideSecret() is invoked at most
+ // once per run, and always before any reference to this flag.)
+ bool create_pairing_;
+
+ // Chromium code's connection to the OBJ_C message loop. Once created the
+ // MessageLoop will live for the life of the program. An attempt was made to
+ // create the primary message loop earlier in the programs life, but a
+ // MessageLoop requires ARC to be disabled.
+ base::MessageLoopForUI* ui_loop_;
+
+ // References to native threads.
+ scoped_refptr<AutoThreadTaskRunner> ui_task_runner_;
+ scoped_refptr<AutoThreadTaskRunner> network_task_runner_;
+
+ scoped_refptr<net::URLRequestContextGetter> url_requester_;
+
+ friend class base::RefCountedThreadSafe<ClientInstance>;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientInstance);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_IOS_BRIDGE_CLIENT_INSTANCE_H_
diff --git a/remoting/ios/bridge/client_instance_unittest.mm b/remoting/ios/bridge/client_instance_unittest.mm
new file mode 100644
index 0000000000..8470e7accd
--- /dev/null
+++ b/remoting/ios/bridge/client_instance_unittest.mm
@@ -0,0 +1,319 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/ios/bridge/client_instance.h"
+
+#include "base/compiler_specific.h"
+#include "base/run_loop.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/synchronization/waitable_event.h"
+#include "remoting/protocol/libjingle_transport_factory.h"
+#import "testing/gtest_mac.h"
+
+#include "remoting/ios/data_store.h"
+#include "remoting/ios/bridge/client_proxy.h"
+#include "remoting/ios/bridge/client_proxy_delegate_wrapper.h"
+
+@interface ClientProxyDelegateForClientInstanceTester
+ : NSObject<ClientProxyDelegate>
+
+- (void)resetDidReceiveSomething;
+
+// Validating what was received is outside of the scope for this test unit. See
+// ClientProxyUnittest for those tests.
+@property(nonatomic, assign) BOOL didReceiveSomething;
+
+@end
+
+@implementation ClientProxyDelegateForClientInstanceTester
+
+@synthesize didReceiveSomething = _didReceiveSomething;
+
+- (void)resetDidReceiveSomething {
+ _didReceiveSomething = false;
+}
+
+- (void)requestHostPin:(BOOL)pairingSupported {
+ _didReceiveSomething = true;
+}
+
+- (void)connected {
+ _didReceiveSomething = true;
+}
+
+- (void)connectionStatus:(NSString*)statusMessage {
+ _didReceiveSomething = true;
+}
+
+- (void)connectionFailed:(NSString*)errorMessage {
+ _didReceiveSomething = true;
+}
+
+- (void)applyFrame:(const webrtc::DesktopSize&)size
+ stride:(NSInteger)stride
+ data:(uint8_t*)data
+ regions:(const std::vector<webrtc::DesktopRect>&)regions {
+ _didReceiveSomething = true;
+}
+
+- (void)applyCursor:(const webrtc::DesktopSize&)size
+ hotspot:(const webrtc::DesktopVector&)hotspot
+ cursorData:(uint8_t*)data {
+ _didReceiveSomething = true;
+}
+
+@end
+
+namespace remoting {
+
+namespace {
+
+const std::string kHostId = "HostIdTest";
+const std::string kPairingId = "PairingIdTest";
+const std::string kPairingSecret = "PairingSecretTest";
+const std::string kSecretPin = "SecretPinTest";
+
+// TODO(aboone) should be able to call RunLoop().RunUntilIdle() instead but
+// MessagePumpUIApplication::DoRun is marked NOTREACHED()
+void RunCFMessageLoop() {
+ int result;
+ do { // Repeat until no messages remain
+ result = CFRunLoopRunInMode(
+ kCFRunLoopDefaultMode,
+ 0, // Execute queued messages, do not wait for additional messages
+ YES); // Do only one message at a time
+ } while (result != kCFRunLoopRunStopped && result != kCFRunLoopRunFinished &&
+ result != kCFRunLoopRunTimedOut);
+}
+
+void SecretPinCallBack(const std::string& secret) {
+ ASSERT_STREQ(kSecretPin.c_str(), secret.c_str());
+}
+
+} // namespace
+
+class ClientInstanceTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ testDelegate_.reset(
+ [[ClientProxyDelegateForClientInstanceTester alloc] init]);
+ proxy_.reset(new ClientProxy(
+ [ClientProxyDelegateWrapper wrapDelegate:testDelegate_]));
+ instance_ =
+ new ClientInstance(proxy_->AsWeakPtr(), "", "", "", "", "", "", "");
+ }
+ virtual void TearDown() OVERRIDE {
+ // Ensure memory is not leaking
+ // Notice Cleanup is safe to call, regardless of if Start() was ever called.
+ instance_->Cleanup();
+ RunCFMessageLoop();
+ // An object on the network thread which owns a reference to |instance_| may
+ // be cleaned up 'soon', but not immediately. Lets wait it out, up to 1
+ // second.
+ for (int i = 0; i < 100; i++) {
+ if (!instance_->HasOneRef()) {
+ [NSThread sleepForTimeInterval:.01];
+ } else {
+ break;
+ }
+ }
+
+ // Remove the last reference from |instance_|, and destructor is called.
+ ASSERT_TRUE(instance_->HasOneRef());
+ instance_ = NULL;
+ }
+
+ void AssertAcknowledged(BOOL wasAcknowledged) {
+ ASSERT_EQ(wasAcknowledged, [testDelegate_ didReceiveSomething]);
+ // Reset for the next test
+ [testDelegate_ resetDidReceiveSomething];
+ }
+
+ void TestStatusAndError(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) {
+ instance_->OnConnectionState(state, error);
+ AssertAcknowledged(true);
+ }
+
+ void TestConnectionStatus(protocol::ConnectionToHost::State state) {
+ TestStatusAndError(state, protocol::ErrorCode::OK);
+ TestStatusAndError(state, protocol::ErrorCode::PEER_IS_OFFLINE);
+ TestStatusAndError(state, protocol::ErrorCode::SESSION_REJECTED);
+ TestStatusAndError(state, protocol::ErrorCode::INCOMPATIBLE_PROTOCOL);
+ TestStatusAndError(state, protocol::ErrorCode::AUTHENTICATION_FAILED);
+ TestStatusAndError(state, protocol::ErrorCode::CHANNEL_CONNECTION_ERROR);
+ TestStatusAndError(state, protocol::ErrorCode::SIGNALING_ERROR);
+ TestStatusAndError(state, protocol::ErrorCode::SIGNALING_TIMEOUT);
+ TestStatusAndError(state, protocol::ErrorCode::HOST_OVERLOAD);
+ TestStatusAndError(state, protocol::ErrorCode::UNKNOWN_ERROR);
+ }
+
+ base::scoped_nsobject<ClientProxyDelegateForClientInstanceTester>
+ testDelegate_;
+ scoped_ptr<ClientProxy> proxy_;
+ scoped_refptr<ClientInstance> instance_;
+};
+
+TEST_F(ClientInstanceTest, Create) {
+ // This is a test for memory leaking. Ensure a completely unused instance of
+ // ClientInstance is destructed.
+
+ ASSERT_TRUE(instance_ != NULL);
+ ASSERT_TRUE(instance_->HasOneRef());
+}
+
+TEST_F(ClientInstanceTest, CreateAndStart) {
+ // This is a test for memory leaking. Ensure a properly used instance of
+ // ClientInstance is destructed.
+
+ ASSERT_TRUE(instance_ != NULL);
+ ASSERT_TRUE(instance_->HasOneRef());
+
+ instance_->Start();
+ RunCFMessageLoop();
+ ASSERT_TRUE(!instance_->HasOneRef()); // more than one
+}
+
+TEST_F(ClientInstanceTest, SecretPin) {
+ NSString* hostId = [NSString stringWithUTF8String:kHostId.c_str()];
+ NSString* pairingId = [NSString stringWithUTF8String:kPairingId.c_str()];
+ NSString* pairingSecret =
+ [NSString stringWithUTF8String:kPairingSecret.c_str()];
+
+ DataStore* store = [DataStore sharedStore];
+
+ const HostPreferences* host = [store createHost:hostId];
+ host.pairId = pairingId;
+ host.pairSecret = pairingSecret;
+ [store saveChanges];
+
+ // Suggesting that our pairing Id is known, but since its obviously not we
+ // expect it to be discarded when requesting the PIN.
+ instance_ = new ClientInstance(
+ proxy_->AsWeakPtr(), "", "", "", kHostId, "", kPairingId, kPairingSecret);
+
+ instance_->Start();
+ RunCFMessageLoop();
+
+ instance_->FetchSecret(false, base::Bind(&SecretPinCallBack));
+ RunCFMessageLoop();
+ AssertAcknowledged(true);
+
+ // The pairing information was discarded
+ host = [store getHostForId:hostId];
+ ASSERT_NSEQ(@"", host.pairId);
+ ASSERT_NSEQ(@"", host.pairSecret);
+
+ instance_->ProvideSecret(kSecretPin, false);
+ RunCFMessageLoop();
+}
+
+TEST_F(ClientInstanceTest, NoProxy) {
+ // After the proxy is released, we still expect quite a few functions to be
+ // able to run, but not produce any output. Some of these are just being
+ // executed for code coverage, the outputs are not pertinent to this test
+ // unit.
+ proxy_.reset();
+
+ instance_->Start();
+ RunCFMessageLoop();
+
+ instance_->FetchSecret(false, base::Bind(&SecretPinCallBack));
+ AssertAcknowledged(false);
+
+ instance_->ProvideSecret(kSecretPin, false);
+ AssertAcknowledged(false);
+
+ instance_->PerformMouseAction(
+ webrtc::DesktopVector(0, 0), webrtc::DesktopVector(0, 0), 0, false);
+ AssertAcknowledged(false);
+
+ instance_->PerformKeyboardAction(0, false);
+ AssertAcknowledged(false);
+
+ instance_->OnConnectionState(protocol::ConnectionToHost::State::CONNECTED,
+ protocol::ErrorCode::OK);
+ AssertAcknowledged(false);
+
+ instance_->OnConnectionReady(false);
+ AssertAcknowledged(false);
+
+ instance_->OnRouteChanged("", protocol::TransportRoute());
+ AssertAcknowledged(false);
+
+ // SetCapabilities requires a host connection to be established
+ // instance_->SetCapabilities("");
+ // AssertAcknowledged(false);
+
+ instance_->SetPairingResponse(protocol::PairingResponse());
+ AssertAcknowledged(false);
+
+ instance_->DeliverHostMessage(protocol::ExtensionMessage());
+ AssertAcknowledged(false);
+
+ ASSERT_TRUE(instance_->GetClipboardStub() != NULL);
+ ASSERT_TRUE(instance_->GetCursorShapeStub() != NULL);
+ ASSERT_TRUE(instance_->GetTokenFetcher("") == NULL);
+
+ instance_->InjectClipboardEvent(protocol::ClipboardEvent());
+ AssertAcknowledged(false);
+
+ instance_->SetCursorShape(protocol::CursorShapeInfo());
+ AssertAcknowledged(false);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateINITIALIZING) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::INITIALIZING);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateCONNECTING) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::CONNECTING);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateAUTHENTICATED) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::AUTHENTICATED);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateCONNECTED) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::CONNECTED);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateFAILED) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::FAILED);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionStateCLOSED) {
+ TestConnectionStatus(protocol::ConnectionToHost::State::CLOSED);
+}
+
+TEST_F(ClientInstanceTest, OnConnectionReady) {
+ instance_->OnConnectionReady(true);
+ AssertAcknowledged(false);
+ instance_->OnConnectionReady(false);
+ AssertAcknowledged(false);
+}
+
+TEST_F(ClientInstanceTest, OnRouteChanged) {
+ // Not expecting anything to happen
+ protocol::TransportRoute route;
+
+ route.type = protocol::TransportRoute::DIRECT;
+ instance_->OnRouteChanged("", route);
+ AssertAcknowledged(false);
+
+ route.type = protocol::TransportRoute::STUN;
+ instance_->OnRouteChanged("", route);
+ AssertAcknowledged(false);
+
+ route.type = protocol::TransportRoute::RELAY;
+ instance_->OnRouteChanged("", route);
+ AssertAcknowledged(false);
+}
+
+TEST_F(ClientInstanceTest, SetCursorShape) {
+ instance_->SetCursorShape(protocol::CursorShapeInfo());
+ AssertAcknowledged(true);
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/bridge/client_proxy.h b/remoting/ios/bridge/client_proxy.h
new file mode 100644
index 0000000000..8088570550
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy.h
@@ -0,0 +1,62 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_HOST_PROXY_H_
+#define REMOTING_IOS_BRIDGE_HOST_PROXY_H_
+
+#include <string>
+
+#include <objc/objc.h>
+#include "base/memory/weak_ptr.h"
+#include "remoting/protocol/connection_to_host.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+
+#if defined(__OBJC__)
+@class ClientProxyDelegateWrapper;
+#else // __OBJC__
+class ClientProxyDelegateWrapper;
+#endif // __OBJC__
+
+namespace remoting {
+
+// Proxies incoming common Chromoting protocol (HOST) to the UI Application
+// (CLIENT). The HOST will have a Weak reference to call member functions on
+// the UI Thread.
+class ClientProxy : public base::SupportsWeakPtr<ClientProxy> {
+ public:
+ ClientProxy(ClientProxyDelegateWrapper* wrapper);
+
+ // Notifies the user of the current connection status.
+ void ReportConnectionStatus(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error);
+
+ // Display a dialog box asking the user to enter a PIN.
+ void DisplayAuthenticationPrompt(bool pairing_supported);
+
+ // Saves new pairing credentials to permanent storage.
+ void CommitPairingCredentials(const std::string& hostId,
+ const std::string& pairId,
+ const std::string& pairSecret);
+
+ // Delivers the latest image buffer for the canvas.
+ void RedrawCanvas(const webrtc::DesktopSize& view_size,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region);
+
+ // Updates cursor.
+ void UpdateCursorShape(const protocol::CursorShapeInfo& cursor_shape);
+
+ private:
+ // Pointer to the UI application which implements the ClientProxyDelegate.
+ // (id) is similar to a (void*) |delegate_| is set from accepting a
+ // strongly typed @interface which wraps the @protocol ClientProxyDelegate.
+ // see comments for host_proxy_delegate_wrapper.h
+ id delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientProxy);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_IOS_BRIDGE_HOST_PROXY_H_
diff --git a/remoting/ios/bridge/client_proxy.mm b/remoting/ios/bridge/client_proxy.mm
new file mode 100644
index 0000000000..8568b9d55e
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy.mm
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#include "remoting/ios/bridge/client_proxy.h"
+
+#import "remoting/ios/data_store.h"
+#import "remoting/ios/host_preferences.h"
+#import "remoting/ios/bridge/client_proxy_delegate_wrapper.h"
+
+namespace {
+// The value indicating a successful connection has been established via a call
+// to ReportConnectionStatus
+const static int kSuccessfulConnection = 3;
+
+// Translate a connection status code integer to a NSString description
+NSString* GetStatusMsgFromInteger(NSInteger code) {
+ switch (code) {
+ case 0: // INITIALIZING
+ return @"Initializing connection";
+ case 1: // CONNECTING
+ return @"Connecting";
+ case 2: // AUTHENTICATED
+ return @"Authenticated";
+ case 3: // CONNECTED
+ return @"Connected";
+ case 4: // FAILED
+ return @"Connection Failed";
+ case 5: // CLOSED
+ return @"Connection closed";
+ default:
+ return @"Unknown connection state";
+ }
+}
+
+// Translate a connection error code integer to a NSString description
+NSString* GetErrorMsgFromInteger(NSInteger code) {
+ switch (code) {
+ case 1: // PEER_IS_OFFLINE
+ return @"Requested host is offline.";
+ case 2: // SESSION_REJECTED
+ return @"Session was rejected by the host.";
+ case 3: // INCOMPATIBLE_PROTOCOL
+ return @"Incompatible Protocol.";
+ case 4: // AUTHENTICATION_FAILED
+ return @"Authentication Failed.";
+ case 5: // CHANNEL_CONNECTION_ERROR
+ return @"Channel Connection Error";
+ case 6: // SIGNALING_ERROR
+ return @"Signaling Error";
+ case 7: // SIGNALING_TIMEOUT
+ return @"Signaling Timeout";
+ case 8: // HOST_OVERLOAD
+ return @"Host Overload";
+ case 9: // UNKNOWN_ERROR
+ return @"An unknown error has occurred, preventing the session "
+ "from opening.";
+ default:
+ return @"An unknown error code has occurred.";
+ }
+}
+
+} // namespace
+
+namespace remoting {
+
+ClientProxy::ClientProxy(ClientProxyDelegateWrapper* wrapper) {
+ delegate_ = [wrapper delegate];
+}
+
+void ClientProxy::ReportConnectionStatus(
+ protocol::ConnectionToHost::State state,
+ protocol::ErrorCode error) {
+ DCHECK(delegate_);
+ if (state <= kSuccessfulConnection && error == protocol::ErrorCode::OK) {
+ // Report Progress
+ [delegate_ connectionStatus:GetStatusMsgFromInteger(state)];
+
+ if (state == kSuccessfulConnection) {
+ [delegate_ connected];
+ }
+ } else {
+ [delegate_ connectionStatus:GetStatusMsgFromInteger(state)];
+ if (error != protocol::ErrorCode::OK) {
+ [delegate_ connectionFailed:GetErrorMsgFromInteger(error)];
+ }
+ }
+}
+
+void ClientProxy::DisplayAuthenticationPrompt(bool pairing_supported) {
+ DCHECK(delegate_);
+ [delegate_ requestHostPin:pairing_supported];
+}
+
+void ClientProxy::CommitPairingCredentials(const std::string& hostId,
+ const std::string& pairId,
+ const std::string& pairSecret) {
+ DCHECK(delegate_);
+ NSString* nsHostId = [[NSString alloc] initWithUTF8String:hostId.c_str()];
+ NSString* nsPairId = [[NSString alloc] initWithUTF8String:pairId.c_str()];
+ NSString* nsPairSecret =
+ [[NSString alloc] initWithUTF8String:pairSecret.c_str()];
+
+ const HostPreferences* hostPrefs =
+ [[DataStore sharedStore] getHostForId:nsHostId];
+ if (hostPrefs == nil) {
+ hostPrefs = [[DataStore sharedStore] createHost:nsHostId];
+ }
+ if (hostPrefs) {
+ hostPrefs.pairId = nsPairId;
+ hostPrefs.pairSecret = nsPairSecret;
+
+ [[DataStore sharedStore] saveChanges];
+ }
+}
+
+void ClientProxy::RedrawCanvas(const webrtc::DesktopSize& view_size,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region) {
+ DCHECK(delegate_);
+ std::vector<webrtc::DesktopRect> regions;
+
+ for (webrtc::DesktopRegion::Iterator i(region); !i.IsAtEnd(); i.Advance()) {
+ const webrtc::DesktopRect& rect(i.rect());
+
+ regions.push_back(webrtc::DesktopRect::MakeXYWH(
+ rect.left(), rect.top(), rect.width(), rect.height()));
+ }
+
+ [delegate_ applyFrame:view_size
+ stride:buffer->stride()
+ data:buffer->data()
+ regions:regions];
+}
+
+void ClientProxy::UpdateCursorShape(
+ const protocol::CursorShapeInfo& cursor_shape) {
+ DCHECK(delegate_);
+ [delegate_ applyCursor:webrtc::DesktopSize(cursor_shape.width(),
+ cursor_shape.height())
+ hotspot:webrtc::DesktopVector(cursor_shape.hotspot_x(),
+ cursor_shape.hotspot_y())
+ cursorData:(uint8_t*)cursor_shape.data().c_str()];
+}
+
+} // namespace remoting
diff --git a/remoting/ios/bridge/client_proxy_delegate.h b/remoting/ios/bridge/client_proxy_delegate.h
new file mode 100644
index 0000000000..7810a2a8a9
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy_delegate.h
@@ -0,0 +1,43 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_H_
+#define REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#include <vector>
+
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+// Contract to provide for callbacks from the common Chromoting protocol to the
+// UI Application.
+@protocol ClientProxyDelegate<NSObject>
+
+// HOST request for client to input their PIN.
+- (void)requestHostPin:(BOOL)pairingSupported;
+
+// HOST notification that a connection has been successfully opened.
+- (void)connected;
+
+// HOST notification for a change in connections status.
+- (void)connectionStatus:(NSString*)statusMessage;
+
+// HOST notification that a connection has failed.
+- (void)connectionFailed:(NSString*)errorMessage;
+
+// A new Canvas (desktop) update has arrived.
+- (void)applyFrame:(const webrtc::DesktopSize&)size
+ stride:(NSInteger)stride
+ data:(uint8_t*)data
+ regions:(const std::vector<webrtc::DesktopRect>&)regions;
+
+// A new Cursor (mouse) update has arrived.
+- (void)applyCursor:(const webrtc::DesktopSize&)size
+ hotspot:(const webrtc::DesktopVector&)hotspot
+ cursorData:(uint8_t*)data;
+@end
+
+#endif // REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_H_ \ No newline at end of file
diff --git a/remoting/ios/bridge/client_proxy_delegate_wrapper.h b/remoting/ios/bridge/client_proxy_delegate_wrapper.h
new file mode 100644
index 0000000000..e57045bce3
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy_delegate_wrapper.h
@@ -0,0 +1,33 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_WRAPPER_H_
+#define REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_WRAPPER_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#include <vector>
+
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+#import "remoting/ios/bridge/client_proxy_delegate.h"
+
+// Wraps ClientProxyDelegate in a class so C++ can accept a strongly typed
+// pointer. C++ does not understand the id<> convention of passing around a
+// OBJ_C @protocol pointer. So the @protocol is wrapped and the class is passed
+// around. After accepting an instance of ClientProxyDelegateWrapper, the
+// @protocol can be referenced as type (id), which is similar to a (void*),
+
+@interface ClientProxyDelegateWrapper : NSObject
+
+@property(nonatomic, retain, readonly) id<ClientProxyDelegate> delegate;
+
+- (id)init __unavailable;
+
++ (id)wrapDelegate:(id<ClientProxyDelegate>)delegate;
+
+@end
+
+#endif // REMOTING_IOS_BRIDGE_HOST_PROXY_DELEGATE_WRAPPER_H_
diff --git a/remoting/ios/bridge/client_proxy_delegate_wrapper.mm b/remoting/ios/bridge/client_proxy_delegate_wrapper.mm
new file mode 100644
index 0000000000..af558dc871
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy_delegate_wrapper.mm
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/bridge/client_proxy_delegate_wrapper.h"
+
+@interface ClientProxyDelegateWrapper (Private)
+- (id)initWithDelegate:(id<ClientProxyDelegate>)delegate;
+@end
+
+@implementation ClientProxyDelegateWrapper
+
+@synthesize delegate = _delegate;
+
+- (id)initWithDelegate:(id<ClientProxyDelegate>)delegate {
+ self = [super init];
+ if (self) {
+ _delegate = delegate;
+ }
+ return self;
+}
+
++ (id)wrapDelegate:(id<ClientProxyDelegate>)delegate {
+ return [[ClientProxyDelegateWrapper alloc] initWithDelegate:delegate];
+}
+
+@end
diff --git a/remoting/ios/bridge/client_proxy_unittest.mm b/remoting/ios/bridge/client_proxy_unittest.mm
new file mode 100644
index 0000000000..16242983f1
--- /dev/null
+++ b/remoting/ios/bridge/client_proxy_unittest.mm
@@ -0,0 +1,366 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/bridge/client_proxy.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+#import "remoting/ios/data_store.h"
+#import "remoting/ios/bridge/client_proxy_delegate_wrapper.h"
+
+@interface ClientProxyDelegateTester : NSObject<ClientProxyDelegate>
+@property(nonatomic, assign) BOOL isConnected;
+@property(nonatomic, copy) NSString* statusMessage;
+@property(nonatomic, copy) NSString* errorMessage;
+@property(nonatomic, assign) BOOL isPairingSupported;
+@property(nonatomic, assign) webrtc::DesktopSize size;
+@property(nonatomic, assign) NSInteger stride;
+@property(nonatomic, assign) uint8_t* data;
+@property(nonatomic, assign) std::vector<webrtc::DesktopRect> regions;
+@property(nonatomic, assign) webrtc::DesktopVector hotspot;
+@end
+
+@implementation ClientProxyDelegateTester
+
+@synthesize isConnected = _isConnected;
+@synthesize statusMessage = _statusMessage;
+@synthesize errorMessage = _errorMessage;
+@synthesize isPairingSupported = _isPairingSupported;
+@synthesize size = _size;
+@synthesize stride = _stride;
+@synthesize data = _data;
+@synthesize regions = _regions;
+@synthesize hotspot = _hotspot;
+
+- (void)connected {
+ _isConnected = true;
+}
+
+- (void)connectionStatus:(NSString*)statusMessage {
+ _statusMessage = statusMessage;
+}
+
+- (void)connectionFailed:(NSString*)errorMessage {
+ _errorMessage = errorMessage;
+}
+
+- (void)requestHostPin:(BOOL)pairingSupported {
+ _isPairingSupported = pairingSupported;
+}
+
+- (void)applyFrame:(const webrtc::DesktopSize&)size
+ stride:(NSInteger)stride
+ data:(uint8_t*)data
+ regions:(const std::vector<webrtc::DesktopRect>&)regions {
+ _size = size;
+ _stride = stride;
+ _data = data;
+ _regions.assign(regions.begin(), regions.end());
+}
+
+- (void)applyCursor:(const webrtc::DesktopSize&)size
+ hotspot:(const webrtc::DesktopVector&)hotspot
+ cursorData:(uint8_t*)data {
+ _size = size;
+ _hotspot = hotspot;
+ _data = data;
+}
+
+@end
+
+namespace remoting {
+
+namespace {
+
+NSString* kStatusINITIALIZING = @"Initializing connection";
+NSString* kStatusCONNECTING = @"Connecting";
+NSString* kStatusAUTHENTICATED = @"Authenticated";
+NSString* kStatusCONNECTED = @"Connected";
+NSString* kStatusFAILED = @"Connection Failed";
+NSString* kStatusCLOSED = @"Connection closed";
+NSString* kStatusDEFAULT = @"Unknown connection state";
+
+NSString* kErrorPEER_IS_OFFLINE = @"Requested host is offline.";
+NSString* kErrorSESSION_REJECTED = @"Session was rejected by the host.";
+NSString* kErrorINCOMPATIBLE_PROTOCOL = @"Incompatible Protocol.";
+NSString* kErrorAUTHENTICATION_FAILED = @"Authentication Failed.";
+NSString* kErrorCHANNEL_CONNECTION_ERROR = @"Channel Connection Error";
+NSString* kErrorSIGNALING_ERROR = @"Signaling Error";
+NSString* kErrorSIGNALING_TIMEOUT = @"Signaling Timeout";
+NSString* kErrorHOST_OVERLOAD = @"Host Overload";
+NSString* kErrorUNKNOWN_ERROR =
+ @"An unknown error has occurred, preventing the session from opening.";
+NSString* kErrorDEFAULT = @"An unknown error code has occurred.";
+
+const webrtc::DesktopSize kFrameSize(100, 100);
+
+// Note these are disjoint regions. Testing intersecting regions is beyond the
+// scope of this test class.
+const webrtc::DesktopRect kFrameSubRect1 =
+ webrtc::DesktopRect::MakeXYWH(0, 0, 10, 10);
+const webrtc::DesktopRect kFrameSubRect2 =
+ webrtc::DesktopRect::MakeXYWH(11, 11, 10, 10);
+const webrtc::DesktopRect kFrameSubRect3 =
+ webrtc::DesktopRect::MakeXYWH(22, 22, 10, 10);
+
+const int kCursorHeight = 10;
+const int kCursorWidth = 20;
+const int kCursorHotSpotX = 4;
+const int kCursorHotSpotY = 8;
+// |kCursorDataLength| is assumed to be evenly divisible by 4
+const int kCursorDataLength = kCursorHeight * kCursorWidth;
+const uint32_t kCursorDataPattern = 0xF0E1D2C3;
+
+const std::string kHostName = "ClientProxyHostNameTest";
+const std::string kPairingId = "ClientProxyPairingIdTest";
+const std::string kPairingSecret = "ClientProxyPairingSecretTest";
+
+} // namespace
+
+class ClientProxyTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ delegateTester_ = [[ClientProxyDelegateTester alloc] init];
+ clientProxy_.reset(new ClientProxy(
+ [ClientProxyDelegateWrapper wrapDelegate:delegateTester_]));
+ }
+
+ void ResetIsConnected() { delegateTester_.isConnected = false; }
+
+ void TestConnnectionStatus(protocol::ConnectionToHost::State state,
+ NSString* expectedStatusMsg) {
+ ResetIsConnected();
+ clientProxy_->ReportConnectionStatus(state, protocol::ErrorCode::OK);
+ EXPECT_NSEQ(expectedStatusMsg, delegateTester_.statusMessage);
+
+ if (state == protocol::ConnectionToHost::State::CONNECTED) {
+ EXPECT_TRUE(delegateTester_.isConnected);
+ } else {
+ EXPECT_FALSE(delegateTester_.isConnected);
+ }
+
+ TestErrorMessages(state, expectedStatusMsg);
+ }
+
+ void TestForError(protocol::ConnectionToHost::State state,
+ protocol::ErrorCode errorCode,
+ NSString* expectedStatusMsg,
+ NSString* expectedErrorMsg) {
+ ResetIsConnected();
+ clientProxy_->ReportConnectionStatus(state, errorCode);
+ EXPECT_FALSE(delegateTester_.isConnected);
+ EXPECT_NSEQ(expectedStatusMsg, delegateTester_.statusMessage);
+ EXPECT_NSEQ(expectedErrorMsg, delegateTester_.errorMessage);
+ }
+
+ void TestErrorMessages(protocol::ConnectionToHost::State state,
+ NSString* expectedStatusMsg) {
+ TestForError(state,
+ protocol::ErrorCode::AUTHENTICATION_FAILED,
+ expectedStatusMsg,
+ kErrorAUTHENTICATION_FAILED);
+ TestForError(state,
+ protocol::ErrorCode::CHANNEL_CONNECTION_ERROR,
+ expectedStatusMsg,
+ kErrorCHANNEL_CONNECTION_ERROR);
+ TestForError(state,
+ protocol::ErrorCode::HOST_OVERLOAD,
+ expectedStatusMsg,
+ kErrorHOST_OVERLOAD);
+ TestForError(state,
+ protocol::ErrorCode::INCOMPATIBLE_PROTOCOL,
+ expectedStatusMsg,
+ kErrorINCOMPATIBLE_PROTOCOL);
+ TestForError(state,
+ protocol::ErrorCode::PEER_IS_OFFLINE,
+ expectedStatusMsg,
+ kErrorPEER_IS_OFFLINE);
+ TestForError(state,
+ protocol::ErrorCode::SESSION_REJECTED,
+ expectedStatusMsg,
+ kErrorSESSION_REJECTED);
+ TestForError(state,
+ protocol::ErrorCode::SIGNALING_ERROR,
+ expectedStatusMsg,
+ kErrorSIGNALING_ERROR);
+ TestForError(state,
+ protocol::ErrorCode::SIGNALING_TIMEOUT,
+ expectedStatusMsg,
+ kErrorSIGNALING_TIMEOUT);
+ TestForError(state,
+ protocol::ErrorCode::UNKNOWN_ERROR,
+ expectedStatusMsg,
+ kErrorUNKNOWN_ERROR);
+ TestForError(state,
+ static_cast<protocol::ErrorCode>(999),
+ expectedStatusMsg,
+ kErrorDEFAULT);
+ }
+
+ void ValidateHost(const std::string& hostName,
+ const std::string& pairingId,
+ const std::string& pairingSecret) {
+ DataStore* store = [DataStore sharedStore];
+ NSString* hostNameAsNSString =
+ [NSString stringWithUTF8String:hostName.c_str()];
+ const HostPreferences* host = [store getHostForId:hostNameAsNSString];
+ if (host != nil) {
+ [store removeHost:host];
+ }
+
+ clientProxy_->CommitPairingCredentials(hostName, pairingId, pairingSecret);
+
+ host = [store getHostForId:hostNameAsNSString];
+
+ ASSERT_TRUE(host != nil);
+ ASSERT_STREQ(hostName.c_str(), [host.hostId UTF8String]);
+ ASSERT_STREQ(pairingId.c_str(), [host.pairId UTF8String]);
+ ASSERT_STREQ(pairingSecret.c_str(), [host.pairSecret UTF8String]);
+ }
+
+ scoped_ptr<ClientProxy> clientProxy_;
+ ClientProxyDelegateTester* delegateTester_;
+ ClientProxyDelegateWrapper* delegateWrapper_;
+};
+
+TEST_F(ClientProxyTest, ReportConnectionStatusINITIALIZING) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::INITIALIZING,
+ kStatusINITIALIZING);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusCONNECTING) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::CONNECTING,
+ kStatusCONNECTING);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusAUTHENTICATED) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::AUTHENTICATED,
+ kStatusAUTHENTICATED);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusCONNECTED) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::CONNECTED,
+ kStatusCONNECTED);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusFAILED) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::FAILED,
+ kStatusFAILED);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusCLOSED) {
+ TestConnnectionStatus(protocol::ConnectionToHost::State::CLOSED,
+ kStatusCLOSED);
+}
+
+TEST_F(ClientProxyTest, ReportConnectionStatusDEFAULT) {
+ TestConnnectionStatus(static_cast<protocol::ConnectionToHost::State>(999),
+ kStatusDEFAULT);
+}
+
+TEST_F(ClientProxyTest, DisplayAuthenticationPrompt) {
+ clientProxy_->DisplayAuthenticationPrompt(true);
+ ASSERT_TRUE(delegateTester_.isPairingSupported);
+ clientProxy_->DisplayAuthenticationPrompt(false);
+ ASSERT_FALSE(delegateTester_.isPairingSupported);
+}
+
+TEST_F(ClientProxyTest, CommitPairingCredentialsBasic) {
+ ValidateHost("", "", "");
+}
+
+TEST_F(ClientProxyTest, CommitPairingCredentialsExtended) {
+ ValidateHost(kHostName, kPairingId, kPairingSecret);
+}
+
+TEST_F(ClientProxyTest, RedrawCanvasBasic) {
+
+ webrtc::BasicDesktopFrame frame(webrtc::DesktopSize(1, 1));
+ webrtc::DesktopRegion regions;
+ regions.AddRect(webrtc::DesktopRect::MakeLTRB(0, 0, 1, 1));
+
+ clientProxy_->RedrawCanvas(webrtc::DesktopSize(1, 1), &frame, regions);
+
+ ASSERT_TRUE(webrtc::DesktopSize(1, 1).equals(delegateTester_.size));
+ ASSERT_EQ(4, delegateTester_.stride);
+ ASSERT_TRUE(delegateTester_.data != NULL);
+ ASSERT_EQ(1, delegateTester_.regions.size());
+ ASSERT_TRUE(delegateTester_.regions[0].equals(
+ webrtc::DesktopRect::MakeLTRB(0, 0, 1, 1)));
+}
+TEST_F(ClientProxyTest, RedrawCanvasExtended) {
+
+ webrtc::BasicDesktopFrame frame(kFrameSize);
+ webrtc::DesktopRegion regions;
+ regions.AddRect(kFrameSubRect1);
+ regions.AddRect(kFrameSubRect2);
+ regions.AddRect(kFrameSubRect3);
+
+ clientProxy_->RedrawCanvas(kFrameSize, &frame, regions);
+
+ ASSERT_TRUE(kFrameSize.equals(delegateTester_.size));
+ ASSERT_EQ(kFrameSize.width() * webrtc::DesktopFrame::kBytesPerPixel,
+ delegateTester_.stride);
+ ASSERT_TRUE(delegateTester_.data != NULL);
+ ASSERT_EQ(3, delegateTester_.regions.size());
+ ASSERT_TRUE(delegateTester_.regions[0].equals(kFrameSubRect1));
+ ASSERT_TRUE(delegateTester_.regions[1].equals(kFrameSubRect2));
+ ASSERT_TRUE(delegateTester_.regions[2].equals(kFrameSubRect3));
+}
+
+TEST_F(ClientProxyTest, UpdateCursorBasic) {
+ protocol::CursorShapeInfo cursor_proto;
+ cursor_proto.set_width(1);
+ cursor_proto.set_height(1);
+ cursor_proto.set_hotspot_x(0);
+ cursor_proto.set_hotspot_y(0);
+
+ char data[4];
+ memset(data, 0xFF, 4);
+
+ cursor_proto.set_data(data);
+
+ clientProxy_->UpdateCursorShape(cursor_proto);
+
+ ASSERT_EQ(1, delegateTester_.size.width());
+ ASSERT_EQ(1, delegateTester_.size.height());
+ ASSERT_EQ(0, delegateTester_.hotspot.x());
+ ASSERT_EQ(0, delegateTester_.hotspot.y());
+ ASSERT_TRUE(delegateTester_.data != NULL);
+ for (int i = 0; i < 4; i++) {
+ ASSERT_EQ(0xFF, delegateTester_.data[i]);
+ }
+}
+
+TEST_F(ClientProxyTest, UpdateCursorExtended) {
+ protocol::CursorShapeInfo cursor_proto;
+ cursor_proto.set_width(kCursorWidth);
+ cursor_proto.set_height(kCursorHeight);
+ cursor_proto.set_hotspot_x(kCursorHotSpotX);
+ cursor_proto.set_hotspot_y(kCursorHotSpotY);
+
+ char data[kCursorDataLength];
+ memset_pattern4(data, &kCursorDataPattern, kCursorDataLength);
+
+ cursor_proto.set_data(data);
+
+ clientProxy_->UpdateCursorShape(cursor_proto);
+
+ ASSERT_EQ(kCursorWidth, delegateTester_.size.width());
+ ASSERT_EQ(kCursorHeight, delegateTester_.size.height());
+ ASSERT_EQ(kCursorHotSpotX, delegateTester_.hotspot.x());
+ ASSERT_EQ(kCursorHotSpotY, delegateTester_.hotspot.y());
+ ASSERT_TRUE(delegateTester_.data != NULL);
+ for (int i = 0; i < kCursorDataLength / 4; i++) {
+ ASSERT_TRUE(memcmp(&delegateTester_.data[i * 4], &kCursorDataPattern, 4) ==
+ 0);
+ }
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/bridge/frame_consumer_bridge.cc b/remoting/ios/bridge/frame_consumer_bridge.cc
new file mode 100644
index 0000000000..fbc96066d3
--- /dev/null
+++ b/remoting/ios/bridge/frame_consumer_bridge.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/ios/bridge/frame_consumer_bridge.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/synchronization/waitable_event.h"
+#include "remoting/base/util.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+
+namespace remoting {
+
+FrameConsumerBridge::FrameConsumerBridge(OnFrameCallback callback)
+ : callback_(callback), frame_producer_(NULL) {}
+
+FrameConsumerBridge::~FrameConsumerBridge() {
+ // The producer should now return any pending buffers. At this point, however,
+ // the buffers are returned via tasks which may not be scheduled before the
+ // producer, so we free all the buffers once the producer's queue is empty.
+ // And the scheduled tasks will die quietly.
+ if (frame_producer_) {
+ base::WaitableEvent done_event(true, false);
+ frame_producer_->RequestReturnBuffers(base::Bind(
+ &base::WaitableEvent::Signal, base::Unretained(&done_event)));
+ done_event.Wait();
+ }
+}
+
+void FrameConsumerBridge::Initialize(FrameProducer* producer) {
+ DCHECK(!frame_producer_);
+ frame_producer_ = producer;
+ DCHECK(frame_producer_);
+}
+
+void FrameConsumerBridge::ApplyBuffer(const webrtc::DesktopSize& view_size,
+ const webrtc::DesktopRect& clip_area,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region,
+ const webrtc::DesktopRegion& shape) {
+ DCHECK(frame_producer_);
+ if (!view_size_.equals(view_size)) {
+ // Drop the frame, since the data belongs to the previous generation,
+ // before SetSourceSize() called SetOutputSizeAndClip().
+ ReturnBuffer(buffer);
+ return;
+ }
+
+ // This call completes synchronously.
+ callback_.Run(view_size, buffer, region);
+
+ // Recycle |buffer| by returning it to |frame_producer_| as the next buffer
+ frame_producer_->DrawBuffer(buffer);
+}
+
+void FrameConsumerBridge::ReturnBuffer(webrtc::DesktopFrame* buffer) {
+ DCHECK(frame_producer_);
+ ScopedVector<webrtc::DesktopFrame>::iterator it =
+ std::find(buffers_.begin(), buffers_.end(), buffer);
+
+ DCHECK(it != buffers_.end());
+ buffers_.erase(it);
+}
+
+void FrameConsumerBridge::SetSourceSize(const webrtc::DesktopSize& source_size,
+ const webrtc::DesktopVector& dpi) {
+ DCHECK(frame_producer_);
+ view_size_ = source_size;
+ webrtc::DesktopRect clip_area = webrtc::DesktopRect::MakeSize(view_size_);
+ frame_producer_->SetOutputSizeAndClip(view_size_, clip_area);
+
+ // Now that the size is well known, ask the producer to start drawing
+ DrawWithNewBuffer();
+}
+
+FrameConsumerBridge::PixelFormat FrameConsumerBridge::GetPixelFormat() {
+ return FORMAT_RGBA;
+}
+
+void FrameConsumerBridge::DrawWithNewBuffer() {
+ DCHECK(frame_producer_);
+ webrtc::DesktopFrame* buffer = new webrtc::BasicDesktopFrame(view_size_);
+ buffers_.push_back(buffer);
+ frame_producer_->DrawBuffer(buffer);
+}
+
+} // namespace remoting
diff --git a/remoting/ios/bridge/frame_consumer_bridge.h b/remoting/ios/bridge/frame_consumer_bridge.h
new file mode 100644
index 0000000000..70eca2f0de
--- /dev/null
+++ b/remoting/ios/bridge/frame_consumer_bridge.h
@@ -0,0 +1,65 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_FRAME_CONSUMER_BRIDGE_H_
+#define REMOTING_IOS_BRIDGE_FRAME_CONSUMER_BRIDGE_H_
+
+#include <list>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_vector.h"
+#include "base/memory/weak_ptr.h"
+#include "remoting/client/frame_consumer.h"
+#include "remoting/client/frame_producer.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+namespace remoting {
+
+class FrameConsumerBridge : public base::SupportsWeakPtr<FrameConsumerBridge>,
+ public FrameConsumer {
+ public:
+ typedef base::Callback<void(const webrtc::DesktopSize& view_size,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region)>
+ OnFrameCallback;
+
+ // A callback is provided to return frame updates asynchronously.
+ explicit FrameConsumerBridge(OnFrameCallback callback);
+
+ virtual ~FrameConsumerBridge();
+ // This must be called before any other functional use.
+ void Initialize(FrameProducer* producer);
+
+ // FrameConsumer implementation.
+ virtual void ApplyBuffer(const webrtc::DesktopSize& view_size,
+ const webrtc::DesktopRect& clip_area,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region,
+ const webrtc::DesktopRegion& shape) OVERRIDE;
+ virtual void ReturnBuffer(webrtc::DesktopFrame* buffer) OVERRIDE;
+ virtual void SetSourceSize(const webrtc::DesktopSize& source_size,
+ const webrtc::DesktopVector& dpi) OVERRIDE;
+ virtual PixelFormat GetPixelFormat() OVERRIDE;
+
+ private:
+ // Allocates a new buffer of |view_size_|, and tells the producer to draw onto
+ // it. This can be called as soon as the producer is known, but is not
+ // required until ready to receive frames.
+ void DrawWithNewBuffer();
+
+ OnFrameCallback callback_;
+
+ FrameProducer* frame_producer_;
+ webrtc::DesktopSize view_size_;
+
+ // List of allocated image buffers.
+ ScopedVector<webrtc::DesktopFrame> buffers_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameConsumerBridge);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_IOS_BRIDGE_FRAME_CONSUMER_BRIDGE_H_
diff --git a/remoting/ios/bridge/frame_consumer_bridge_unittest.cc b/remoting/ios/bridge/frame_consumer_bridge_unittest.cc
new file mode 100644
index 0000000000..30d63d0e32
--- /dev/null
+++ b/remoting/ios/bridge/frame_consumer_bridge_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/ios/bridge/frame_consumer_bridge.h"
+
+#include <queue>
+#include <gtest/gtest.h>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_region.h"
+
+namespace {
+const webrtc::DesktopSize kFrameSize(100, 100);
+const webrtc::DesktopVector kDpi(100, 100);
+
+const webrtc::DesktopRect FrameRect() {
+ return webrtc::DesktopRect::MakeSize(kFrameSize);
+}
+
+webrtc::DesktopRegion FrameRegion() {
+ return webrtc::DesktopRegion(FrameRect());
+}
+
+void FrameDelivery(const webrtc::DesktopSize& view_size,
+ webrtc::DesktopFrame* buffer,
+ const webrtc::DesktopRegion& region) {
+ ASSERT_TRUE(view_size.equals(kFrameSize));
+ ASSERT_TRUE(region.Equals(FrameRegion()));
+};
+
+} // namespace
+
+namespace remoting {
+
+class FrameProducerTester : public FrameProducer {
+ public:
+ virtual ~FrameProducerTester() {};
+
+ virtual void DrawBuffer(webrtc::DesktopFrame* buffer) OVERRIDE {
+ frames.push(buffer);
+ };
+
+ virtual void InvalidateRegion(const webrtc::DesktopRegion& region) OVERRIDE {
+ NOTIMPLEMENTED();
+ };
+
+ virtual void RequestReturnBuffers(const base::Closure& done) OVERRIDE {
+ // Don't have to actually return the buffers. This function is really
+ // saying don't use the references anymore, they are now invalid.
+ while (!frames.empty()) {
+ frames.pop();
+ }
+ done.Run();
+ };
+
+ virtual void SetOutputSizeAndClip(const webrtc::DesktopSize& view_size,
+ const webrtc::DesktopRect& clip_area)
+ OVERRIDE {
+ viewSize = view_size;
+ clipArea = clip_area;
+ };
+
+ std::queue<webrtc::DesktopFrame*> frames;
+ webrtc::DesktopSize viewSize;
+ webrtc::DesktopRect clipArea;
+};
+
+class FrameConsumerBridgeTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ frameProducer_.reset(new FrameProducerTester());
+ frameConsumer_.reset(new FrameConsumerBridge(base::Bind(&FrameDelivery)));
+ frameConsumer_->Initialize(frameProducer_.get());
+ }
+ virtual void TearDown() OVERRIDE {}
+
+ scoped_ptr<FrameProducerTester> frameProducer_;
+ scoped_ptr<FrameConsumerBridge> frameConsumer_;
+};
+
+TEST(FrameConsumerBridgeTest_NotInitialized, CreateAndRelease) {
+ scoped_ptr<FrameConsumerBridge> frameConsumer_(
+ new FrameConsumerBridge(base::Bind(&FrameDelivery)));
+ ASSERT_TRUE(frameConsumer_.get() != NULL);
+ frameConsumer_.reset();
+ ASSERT_TRUE(frameConsumer_.get() == NULL);
+}
+
+TEST_F(FrameConsumerBridgeTest, ApplyBuffer) {
+ webrtc::DesktopFrame* frame = NULL;
+ ASSERT_EQ(0, frameProducer_->frames.size());
+ frameConsumer_->SetSourceSize(kFrameSize, kDpi);
+ ASSERT_EQ(1, frameProducer_->frames.size());
+
+ // Return the frame, and ensure we get it back
+ frame = frameProducer_->frames.front();
+ frameProducer_->frames.pop();
+ ASSERT_EQ(0, frameProducer_->frames.size());
+ frameConsumer_->ApplyBuffer(
+ kFrameSize, FrameRect(), frame, FrameRegion(), FrameRegion());
+ ASSERT_EQ(1, frameProducer_->frames.size());
+ ASSERT_TRUE(frame == frameProducer_->frames.front());
+ ASSERT_TRUE(frame->data() == frameProducer_->frames.front()->data());
+
+ // Change the SourceSize, we should get a new frame, but when the old frame is
+ // submitted we will not get it back.
+ frameConsumer_->SetSourceSize(webrtc::DesktopSize(1, 1), kDpi);
+ ASSERT_EQ(2, frameProducer_->frames.size());
+ frame = frameProducer_->frames.front();
+ frameProducer_->frames.pop();
+ ASSERT_EQ(1, frameProducer_->frames.size());
+ frameConsumer_->ApplyBuffer(
+ kFrameSize, FrameRect(), frame, FrameRegion(), FrameRegion());
+ ASSERT_EQ(1, frameProducer_->frames.size());
+}
+
+TEST_F(FrameConsumerBridgeTest, SetSourceSize) {
+ frameConsumer_->SetSourceSize(webrtc::DesktopSize(0, 0),
+ webrtc::DesktopVector(0, 0));
+ ASSERT_TRUE(frameProducer_->viewSize.equals(webrtc::DesktopSize(0, 0)));
+ ASSERT_TRUE(frameProducer_->clipArea.equals(
+ webrtc::DesktopRect::MakeLTRB(0, 0, 0, 0)));
+ ASSERT_EQ(1, frameProducer_->frames.size());
+ ASSERT_TRUE(
+ frameProducer_->frames.front()->size().equals(webrtc::DesktopSize(0, 0)));
+
+ frameConsumer_->SetSourceSize(kFrameSize, kDpi);
+ ASSERT_TRUE(frameProducer_->viewSize.equals(kFrameSize));
+ ASSERT_TRUE(frameProducer_->clipArea.equals(FrameRect()));
+ ASSERT_EQ(2, frameProducer_->frames.size());
+ frameProducer_->frames.pop();
+ ASSERT_TRUE(frameProducer_->frames.front()->size().equals(kFrameSize));
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/bridge/host_proxy.h b/remoting/ios/bridge/host_proxy.h
new file mode 100644
index 0000000000..3c0f129701
--- /dev/null
+++ b/remoting/ios/bridge/host_proxy.h
@@ -0,0 +1,67 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_BRIDGE_CLIENT_PROXY_H_
+#define REMOTING_IOS_BRIDGE_CLIENT_PROXY_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+#import "remoting/ios/bridge/client_proxy_delegate_wrapper.h"
+
+namespace remoting {
+class ClientInstance;
+class ClientProxy;
+} // namespace remoting
+
+// HostProxy is one channel of a bridge from the UI Application (CLIENT) and the
+// common Chromoting protocol (HOST). HostProxy proxies message from the UI
+// application to the host. The reverse channel, ClientProxy, is owned by the
+// HostProxy to control deconstruction order, but is shared with the
+// ClientInstance to perform work.
+
+@interface HostProxy : NSObject {
+ @private
+ // Host to Client channel
+ scoped_ptr<remoting::ClientProxy> _hostToClientChannel;
+ // Client to Host channel, must be released before |_hostToClientChannel|
+ scoped_refptr<remoting::ClientInstance> _clientToHostChannel;
+ // Connection state
+ BOOL _isConnected;
+}
+
+// TRUE when a connection has been established successfully.
+- (BOOL)isConnected;
+
+// Forwards credentials from CLIENT and to HOST and begins establishing a
+// connection.
+- (void)connectToHost:(NSString*)username
+ authToken:(NSString*)token
+ jabberId:(NSString*)jid
+ hostId:(NSString*)hostId
+ publicKey:(NSString*)hostPublicKey
+ delegate:(id<ClientProxyDelegate>)delegate;
+
+// Report from CLIENT with the user's PIN.
+- (void)authenticationResponse:(NSString*)pin createPair:(BOOL)createPair;
+
+// CLIENT initiated disconnection
+- (void)disconnectFromHost;
+
+// Report from CLIENT of mouse input
+- (void)mouseAction:(const webrtc::DesktopVector&)position
+ wheelDelta:(const webrtc::DesktopVector&)wheelDelta
+ whichButton:(NSInteger)buttonPressed
+ buttonDown:(BOOL)buttonIsDown;
+
+// Report from CLIENT of keyboard input
+- (void)keyboardAction:(NSInteger)keyCode keyDown:(BOOL)keyIsDown;
+
+@end
+
+#endif // REMOTING_IOS_BRIDGE_CLIENT_PROXY_H_
diff --git a/remoting/ios/bridge/host_proxy.mm b/remoting/ios/bridge/host_proxy.mm
new file mode 100644
index 0000000000..1573f9c83a
--- /dev/null
+++ b/remoting/ios/bridge/host_proxy.mm
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "remoting/ios/bridge/host_proxy.h"
+
+#import "remoting/ios/data_store.h"
+#import "remoting/ios/host_preferences.h"
+#import "remoting/ios/bridge/client_instance.h"
+#import "remoting/ios/bridge/client_proxy.h"
+
+@implementation HostProxy
+
+// Override default constructor and initialize internals
+- (id)init {
+ self = [super init];
+ if (self) {
+ _isConnected = false;
+ }
+ return self;
+}
+
+// Override default destructor
+- (void)dealloc {
+ if (_isConnected) {
+ [self disconnectFromHost];
+ }
+
+ [super dealloc];
+}
+
+- (BOOL)isConnected {
+ return _isConnected;
+}
+
+- (void)connectToHost:(NSString*)username
+ authToken:(NSString*)token
+ jabberId:(NSString*)jid
+ hostId:(NSString*)hostId
+ publicKey:(NSString*)hostPublicKey
+ delegate:(id<ClientProxyDelegate>)delegate {
+ // Implicitly, if currently connected, discard the connection and begin a new
+ // connection.
+ [self disconnectFromHost];
+
+ NSString* pairId = @"";
+ NSString* pairSecret = @"";
+
+ const HostPreferences* hostPrefs =
+ [[DataStore sharedStore] getHostForId:hostId];
+
+ // Use the pairing id and secret when known
+ if (hostPrefs && hostPrefs.pairId && hostPrefs.pairSecret) {
+ pairId = [hostPrefs.pairId copy];
+ pairSecret = [hostPrefs.pairSecret copy];
+ }
+
+ _hostToClientChannel.reset(new remoting::ClientProxy(
+ [ClientProxyDelegateWrapper wrapDelegate:delegate]));
+
+ DCHECK(!_clientToHostChannel);
+ _clientToHostChannel =
+ new remoting::ClientInstance(_hostToClientChannel->AsWeakPtr(),
+ [username UTF8String],
+ [token UTF8String],
+ [jid UTF8String],
+ [hostId UTF8String],
+ [hostPublicKey UTF8String],
+ [pairId UTF8String],
+ [pairSecret UTF8String]);
+
+ _clientToHostChannel->Start();
+ _isConnected = YES;
+}
+
+- (void)authenticationResponse:(NSString*)pin createPair:(BOOL)createPair {
+ if (_isConnected) {
+ _clientToHostChannel->ProvideSecret([pin UTF8String], createPair);
+ }
+}
+
+- (void)disconnectFromHost {
+ if (_isConnected) {
+ VLOG(1) << "Disconnecting from Host";
+
+ // |_clientToHostChannel| must be closed before releasing
+ // |_hostToClientChannel|
+
+ // |_clientToHostChannel| owns several objects that have references to
+ // itself. These objects need to be cleaned up before we can release
+ // |_clientToHostChannel|.
+ _clientToHostChannel->Cleanup();
+ // All other references to |_clientToHostChannel| should now be free. When
+ // the next statement is executed the destructor is called automatically.
+ _clientToHostChannel = NULL;
+
+ _hostToClientChannel.reset();
+
+ _isConnected = NO;
+ }
+}
+
+- (void)mouseAction:(const webrtc::DesktopVector&)position
+ wheelDelta:(const webrtc::DesktopVector&)wheelDelta
+ whichButton:(NSInteger)buttonPressed
+ buttonDown:(BOOL)buttonIsDown {
+ if (_isConnected) {
+ _clientToHostChannel->PerformMouseAction(
+ position, wheelDelta, buttonPressed, buttonIsDown);
+ }
+}
+
+- (void)keyboardAction:(NSInteger)keyCode keyDown:(BOOL)keyIsDown {
+ if (_isConnected) {
+ _clientToHostChannel->PerformKeyboardAction(keyCode, keyIsDown);
+ }
+}
+
+@end
diff --git a/remoting/ios/bridge/host_proxy_unittest.mm b/remoting/ios/bridge/host_proxy_unittest.mm
new file mode 100644
index 0000000000..9641e63d71
--- /dev/null
+++ b/remoting/ios/bridge/host_proxy_unittest.mm
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/bridge/host_proxy.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+namespace remoting {
+
+class HostProxyTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE { hostProxy_ = [[HostProxy alloc] init]; }
+
+ void CallPassThroughFunctions() {
+ [hostProxy_ mouseAction:webrtc::DesktopVector(0, 0)
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:0
+ buttonDown:NO];
+ [hostProxy_ keyboardAction:0 keyDown:NO];
+ }
+
+ HostProxy* hostProxy_;
+};
+
+TEST_F(HostProxyTest, ConnectDisconnect) {
+ CallPassThroughFunctions();
+
+ ASSERT_FALSE([hostProxy_ isConnected]);
+ [hostProxy_ connectToHost:@""
+ authToken:@""
+ jabberId:@""
+ hostId:@""
+ publicKey:@""
+ delegate:nil];
+ ASSERT_TRUE([hostProxy_ isConnected]);
+
+ CallPassThroughFunctions();
+
+ [hostProxy_ disconnectFromHost];
+ ASSERT_FALSE([hostProxy_ isConnected]);
+
+ CallPassThroughFunctions();
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/data_store.h b/remoting/ios/data_store.h
new file mode 100644
index 0000000000..0e4a34a0c8
--- /dev/null
+++ b/remoting/ios/data_store.h
@@ -0,0 +1,31 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_DATA_STORE_H_
+#define REMOTING_IOS_DATA_STORE_H_
+
+#import <CoreData/CoreData.h>
+
+#import "remoting/ios/host_preferences.h"
+
+// A local data store backed by SQLLite to hold instances of HostPreferences.
+// HostPreference is defined by the Core Data Model templates see
+// ChromotingModel.xcdatamodel
+@interface DataStore : NSObject
+
+// Static pointer to the managed data store
++ (DataStore*)sharedStore;
+
+// General methods
+- (BOOL)saveChanges;
+
+// Access methods for Hosts
+- (NSArray*)allHosts;
+- (const HostPreferences*)createHost:(NSString*)hostId;
+- (void)removeHost:(const HostPreferences*)p;
+- (const HostPreferences*)getHostForId:(NSString*)hostId;
+
+@end
+
+#endif // REMOTING_IOS_DATA_STORE_H_
diff --git a/remoting/ios/data_store.mm b/remoting/ios/data_store.mm
new file mode 100644
index 0000000000..1cfbc3f9ac
--- /dev/null
+++ b/remoting/ios/data_store.mm
@@ -0,0 +1,176 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/data_store.h"
+
+@interface DataStore (Private)
+- (NSString*)itemArchivePath;
+@end
+
+@implementation DataStore {
+ @private
+ NSMutableArray* _allHosts;
+ NSManagedObjectContext* _context;
+ NSManagedObjectModel* _model;
+}
+
+// Create or Get a static data store
++ (DataStore*)sharedStore {
+ static DataStore* sharedStore = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken,
+ ^{ sharedStore = [[super allocWithZone:nil] init]; });
+
+ return sharedStore;
+}
+
+// General methods
++ (id)allocWithZone:(NSZone*)zone {
+ return [self sharedStore];
+}
+
+// Load data store from SQLLite backing store
+- (id)init {
+ self = [super init];
+
+ if (self) {
+ // Read in ChromotingModel.xdatamodeld
+ _model = [NSManagedObjectModel mergedModelFromBundles:nil];
+
+ NSPersistentStoreCoordinator* psc = [[NSPersistentStoreCoordinator alloc]
+ initWithManagedObjectModel:_model];
+
+ NSString* path = [self itemArchivePath];
+ NSURL* storeUrl = [NSURL fileURLWithPath:path];
+
+ NSError* error = nil;
+
+ NSDictionary* tryOptions = @{
+ NSMigratePersistentStoresAutomaticallyOption : @YES,
+ NSInferMappingModelAutomaticallyOption : @YES
+ };
+ NSDictionary* makeOptions =
+ @{NSMigratePersistentStoresAutomaticallyOption : @YES};
+
+ if (![psc addPersistentStoreWithType:NSSQLiteStoreType
+ configuration:nil
+ URL:storeUrl
+ options:tryOptions
+ error:&error]) {
+ // An incompatible version of the store exists, delete it and start over
+ [[NSFileManager defaultManager] removeItemAtURL:storeUrl error:nil];
+
+ [psc addPersistentStoreWithType:NSSQLiteStoreType
+ configuration:nil
+ URL:storeUrl
+ options:makeOptions
+ error:&error];
+ [NSException raise:@"Open failed"
+ format:@"Reason: %@", [error localizedDescription]];
+ }
+
+ // Create the managed object context
+ _context = [[NSManagedObjectContext alloc] init];
+ [_context setPersistentStoreCoordinator:psc];
+
+ // The managed object context can manage undo, but we don't need it
+ [_context setUndoManager:nil];
+
+ _allHosts = nil;
+ }
+ return self;
+}
+
+// Committing to backing store
+- (BOOL)saveChanges {
+ NSError* err = nil;
+ BOOL successful = [_context save:&err];
+ return successful;
+}
+
+// Looking up the backing store path
+- (NSString*)itemArchivePath {
+ NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(
+ NSDocumentDirectory, NSUserDomainMask, YES);
+
+ // Get one and only document directory from that list
+ NSString* documentDirectory = [documentDirectories objectAtIndex:0];
+
+ return [documentDirectory stringByAppendingPathComponent:@"store.data"];
+}
+
+// Return an array of all known hosts, if the list hasn't been loaded yet, then
+// load it now
+- (NSArray*)allHosts {
+ if (!_allHosts) {
+ NSFetchRequest* request = [[NSFetchRequest alloc] init];
+
+ NSEntityDescription* e =
+ [[_model entitiesByName] objectForKey:@"HostPreferences"];
+
+ [request setEntity:e];
+
+ NSError* error;
+ NSArray* result = [_context executeFetchRequest:request error:&error];
+ if (!result) {
+ [NSException raise:@"Fetch failed"
+ format:@"Reason: %@", [error localizedDescription]];
+ }
+ _allHosts = [result mutableCopy];
+ }
+
+ return _allHosts;
+}
+
+// Return a HostPreferences if it already exists, otherwise create a new
+// HostPreferences to use
+- (const HostPreferences*)createHost:(NSString*)hostId {
+
+ const HostPreferences* p = [self getHostForId:hostId];
+
+ if (p == nil) {
+ p = [NSEntityDescription insertNewObjectForEntityForName:@"HostPreferences"
+ inManagedObjectContext:_context];
+ p.hostId = hostId;
+ [_allHosts addObject:p];
+ }
+ return p;
+}
+
+- (void)removeHost:(HostPreferences*)p {
+ [_context deleteObject:p];
+ [_allHosts removeObjectIdenticalTo:p];
+}
+
+// Search the store for any matching HostPreferences
+// return the 1st match or nil
+- (const HostPreferences*)getHostForId:(NSString*)hostId {
+ NSFetchRequest* request = [[NSFetchRequest alloc] init];
+
+ NSEntityDescription* e =
+ [[_model entitiesByName] objectForKey:@"HostPreferences"];
+ [request setEntity:e];
+
+ NSPredicate* predicate =
+ [NSPredicate predicateWithFormat:@"(hostId = %@)", hostId];
+ [request setPredicate:predicate];
+
+ NSError* error;
+ NSArray* result = [_context executeFetchRequest:request error:&error];
+ if (!result) {
+ [NSException raise:@"Fetch failed"
+ format:@"Reason: %@", [error localizedDescription]];
+ }
+
+ for (HostPreferences* curHost in result) {
+ return curHost;
+ }
+ return nil;
+}
+
+@end
diff --git a/remoting/ios/data_store_unittest.mm b/remoting/ios/data_store_unittest.mm
new file mode 100644
index 0000000000..f75dc28ff3
--- /dev/null
+++ b/remoting/ios/data_store_unittest.mm
@@ -0,0 +1,119 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/data_store.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+namespace remoting {
+
+namespace {
+
+NSString* kHostId = @"testHost";
+NSString* kHostPin = @"testHostPin";
+NSString* kPairId = @"testPairId";
+NSString* kPairSecret = @"testPairSecret";
+
+} // namespace
+
+class DataStoreTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ store_ = [[DataStore allocWithZone:nil] init];
+ RemoveAllHosts();
+ EXPECT_EQ(0, HostCount());
+ }
+ virtual void TearDown() OVERRIDE { RemoveAllHosts(); }
+
+ int HostCount() { return [[store_ allHosts] count]; }
+
+ void RemoveAllHosts() {
+ while (HostCount() > 0) {
+ [store_ removeHost:[store_ allHosts].firstObject];
+ }
+ [store_ saveChanges];
+ }
+
+ DataStore* store_;
+};
+
+TEST(DataStoreTest_Static, IsSingleInstance) {
+ DataStore* firstStore = [DataStore sharedStore];
+
+ ASSERT_NSEQ(firstStore, [DataStore sharedStore]);
+}
+
+TEST(DataStoreTest_Static, RemoveAllHost) {
+ // Test this functionality independently before expecting the fixture to do
+ // this correctly during cleanup
+ DataStore* store = [DataStore sharedStore];
+
+ while ([[store allHosts] count]) {
+ [store removeHost:[store allHosts].firstObject];
+ }
+
+ ASSERT_EQ(0, [[store allHosts] count]);
+ store = nil;
+}
+
+TEST_F(DataStoreTest, CreateHost) {
+
+ const HostPreferences* host = [store_ createHost:kHostId];
+ ASSERT_STREQ([kHostId UTF8String], [host.hostId UTF8String]);
+ ASSERT_EQ(1, HostCount());
+}
+
+TEST_F(DataStoreTest, GetHostForId) {
+ const HostPreferences* host = [store_ getHostForId:kHostId];
+ ASSERT_TRUE(host == nil);
+
+ [store_ createHost:kHostId];
+
+ host = [store_ getHostForId:kHostId];
+
+ ASSERT_TRUE(host != nil);
+ ASSERT_STREQ([kHostId UTF8String], [host.hostId UTF8String]);
+}
+
+TEST_F(DataStoreTest, SaveChanges) {
+
+ const HostPreferences* newHost = [store_ createHost:kHostId];
+
+ ASSERT_EQ(1, HostCount());
+
+ // Default values for a new host
+ ASSERT_TRUE([newHost.askForPin boolValue] == NO);
+ ASSERT_TRUE(newHost.hostPin == nil);
+ ASSERT_TRUE(newHost.pairId == nil);
+ ASSERT_TRUE(newHost.pairSecret == nil);
+
+ // Set new values and save
+ newHost.askForPin = [NSNumber numberWithBool:YES];
+ newHost.hostPin = kHostPin;
+ newHost.pairId = kPairId;
+ newHost.pairSecret = kPairSecret;
+
+ [store_ saveChanges];
+
+ // The next time the store is loaded the host will still be present, even
+ // though we are about to release and reinit a new object
+ store_ = nil;
+ store_ = [[DataStore allocWithZone:nil] init];
+ ASSERT_EQ(1, HostCount());
+
+ const HostPreferences* host = [store_ getHostForId:kHostId];
+ ASSERT_TRUE(host != nil);
+ ASSERT_STREQ([kHostId UTF8String], [host.hostId UTF8String]);
+ ASSERT_TRUE([host.askForPin boolValue] == YES);
+ ASSERT_STREQ([kHostPin UTF8String], [host.hostPin UTF8String]);
+ ASSERT_STREQ([kPairId UTF8String], [host.pairId UTF8String]);
+ ASSERT_STREQ([kPairSecret UTF8String], [host.pairSecret UTF8String]);
+}
+
+} // namespace remoting
diff --git a/remoting/ios/host.h b/remoting/ios/host.h
new file mode 100644
index 0000000000..62b4fbd6af
--- /dev/null
+++ b/remoting/ios/host.h
@@ -0,0 +1,28 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_HOST_H_
+#define REMOTING_IOS_HOST_H_
+
+#import <Foundation/Foundation.h>
+
+// A detail record for a Chromoting Host
+@interface Host : NSObject
+
+// Various properties of the Chromoting Host
+@property(nonatomic, copy) NSString* createdTime;
+@property(nonatomic, copy) NSString* hostId;
+@property(nonatomic, copy) NSString* hostName;
+@property(nonatomic, copy) NSString* hostVersion;
+@property(nonatomic, copy) NSString* jabberId;
+@property(nonatomic, copy) NSString* kind;
+@property(nonatomic, copy) NSString* publicKey;
+@property(nonatomic, copy) NSString* status;
+@property(nonatomic, copy) NSString* updatedTime;
+
++ (NSMutableArray*)parseListFromJSON:(NSMutableData*)data;
+
+@end
+
+#endif // REMOTING_IOS_HOST_H_
diff --git a/remoting/ios/host.mm b/remoting/ios/host.mm
new file mode 100644
index 0000000000..592be87914
--- /dev/null
+++ b/remoting/ios/host.mm
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/host.h"
+
+@implementation Host
+
+@synthesize createdTime = _createdTime;
+@synthesize hostId = _hostId;
+@synthesize hostName = _hostName;
+@synthesize hostVersion = _hostVersion;
+@synthesize jabberId = _jabberId;
+@synthesize kind = _kind;
+@synthesize publicKey = _publicKey;
+@synthesize status = _status;
+@synthesize updatedTime = _updatedTime;
+
+// Parse jsonData into Host list
++ (NSMutableArray*)parseListFromJSON:(NSMutableData*)data {
+ NSError* error;
+
+ NSDictionary* json = [NSJSONSerialization JSONObjectWithData:data
+ options:kNilOptions
+ error:&error];
+
+ NSDictionary* dataDict = [json objectForKey:@"data"];
+
+ NSArray* availableServers = [dataDict objectForKey:@"items"];
+
+ NSMutableArray* serverList = [[NSMutableArray alloc] init];
+
+ NSUInteger idx = 0;
+ NSDictionary* svr;
+ NSUInteger count = [availableServers count];
+
+ while (idx < count) {
+ svr = [availableServers objectAtIndex:idx++];
+ Host* host = [[Host alloc] init];
+ host.createdTime = [svr objectForKey:@"createdTime"];
+ host.hostId = [svr objectForKey:@"hostId"];
+ host.hostName = [svr objectForKey:@"hostName"];
+ host.hostVersion = [svr objectForKey:@"hostVersion"];
+ host.jabberId = [svr objectForKey:@"jabberId"];
+ host.kind = [svr objectForKey:@"kind"];
+ host.publicKey = [svr objectForKey:@"publicKey"];
+ host.status = [svr objectForKey:@"status"];
+ host.updatedTime = [svr objectForKey:@"updatedTime"];
+ [serverList addObject:host];
+ }
+
+ return serverList;
+}
+
+@end
diff --git a/remoting/ios/host_cell.h b/remoting/ios/host_cell.h
new file mode 100644
index 0000000000..d18b93359d
--- /dev/null
+++ b/remoting/ios/host_cell.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_HOST_CELL_H_
+#define REMOTING_IOS_HOST_CELL_H_
+
+#import <UIKit/UIKit.h>
+
+// HostCell represents a Host as a row in a tableView, where the row
+// contains a single cell. Several button and outlet are reserved here for
+// future functionality
+@interface HostCell : UITableViewCell
+
+@property(weak, nonatomic) IBOutlet UILabel* labelHostName;
+@property(weak, nonatomic) IBOutlet UILabel* labelStatus;
+
+@end
+
+#endif // REMOTING_IOS_HOST_CELL_H_
diff --git a/remoting/ios/host_cell.mm b/remoting/ios/host_cell.mm
new file mode 100644
index 0000000000..490a94e740
--- /dev/null
+++ b/remoting/ios/host_cell.mm
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/host_cell.h"
+
+@implementation HostCell
+
+// Override UITableViewCell
+- (id)initWithStyle:(UITableViewCellStyle)style
+ reuseIdentifier:(NSString*)reuseIdentifier {
+ self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+ return self;
+}
+
+@end
diff --git a/remoting/ios/host_preferences.h b/remoting/ios/host_preferences.h
new file mode 100644
index 0000000000..6becae59a5
--- /dev/null
+++ b/remoting/ios/host_preferences.h
@@ -0,0 +1,32 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_HOST_PREFERENCES_H_
+#define REMOTING_IOS_HOST_PREFERENCES_H_
+
+#import <CoreData/CoreData.h>
+
+// A HostPreferences contains details to negotiate and maintain a connection
+// to a remote Chromoting host. This is a entity in a backing store. The
+// implementation file is ChromotingModel.xcdatamodeld. If this file is
+// updated, also update the model. The model MUST be properly versioned to
+// ensure backwards compatibility.
+// https://developer.apple.com/library/ios/recipes/xcode_help-core_data_modeling_tool/Articles/creating_new_version.html
+// Or the app must be uninstalled, and reinstalled which will erase the previous
+// version of the backing store.
+@interface HostPreferences : NSManagedObject
+
+// Is a prompt is needed to reconnect or continue the connection to
+// the host
+@property(nonatomic, copy) NSNumber* askForPin;
+// Several properties are populated from the jabber jump server
+@property(nonatomic, copy) NSString* hostId;
+// Supplied by client via UI interaction
+@property(nonatomic, copy) NSString* hostPin;
+@property(nonatomic, copy) NSString* pairId;
+@property(nonatomic, copy) NSString* pairSecret;
+
+@end
+
+#endif // REMOTING_IOS_HOST_PREFERENCES_H_ \ No newline at end of file
diff --git a/remoting/ios/host_refresh.h b/remoting/ios/host_refresh.h
new file mode 100644
index 0000000000..8cdef3efd5
--- /dev/null
+++ b/remoting/ios/host_refresh.h
@@ -0,0 +1,37 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_HOST_REFRESH_H_
+#define REMOTING_IOS_HOST_REFRESH_H_
+
+#import <Foundation/Foundation.h>
+
+#import "GTMOAuth2Authentication.h"
+
+// HostRefresh encapsulates a fetch of the Chromoting host list from
+// the jabber service.
+
+// Contract to handle the host list result of a Chromoting host list fetch.
+@protocol HostRefreshDelegate<NSObject>
+
+- (void)hostListRefresh:(NSArray*)hostList errorMessage:(NSString*)errorMessage;
+
+@end
+
+// Fetches the host list from the jabber service async. Authenticates,
+// and parses the results to provide to a HostListViewController
+@interface HostRefresh : NSObject<NSURLConnectionDataDelegate>
+
+// Store data read while the connection is active, and can be used after the
+// connection has been discarded
+@property(nonatomic, copy) NSMutableData* jsonData;
+@property(nonatomic, copy) NSString* errorMessage;
+@property(nonatomic, assign) id<HostRefreshDelegate> delegate;
+
+- (void)refreshHostList:(GTMOAuth2Authentication*)authReq
+ delegate:(id<HostRefreshDelegate>)delegate;
+
+@end
+
+#endif // REMOTING_IOS_HOST_REFRESH_H_
diff --git a/remoting/ios/host_refresh.mm b/remoting/ios/host_refresh.mm
new file mode 100644
index 0000000000..bf5e67ee5b
--- /dev/null
+++ b/remoting/ios/host_refresh.mm
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/host_refresh.h"
+
+#import "remoting/ios/authorize.h"
+#import "remoting/ios/host.h"
+#import "remoting/ios/utility.h"
+
+namespace {
+NSString* kDefaultErrorMessage = @"The Host list refresh is not available at "
+ @"this time. Please try again later.";
+} // namespace
+
+@interface HostRefresh (Private)
+- (void)authentication:(GTMOAuth2Authentication*)auth
+ request:(NSMutableURLRequest*)request
+ error:(NSError*)error;
+- (void)formatErrorMessage:(NSString*)error;
+- (void)notifyDelegate;
+@end
+
+// Logic flow begins with refreshHostList, and continues until an error occurs,
+// or the host list is returned to the delegate
+@implementation HostRefresh
+
+@synthesize jsonData = _jsonData;
+@synthesize errorMessage = _errorMessage;
+@synthesize delegate = _delegate;
+
+// Override default constructor and initialize internals
+- (id)init {
+ self = [super init];
+ if (self) {
+ _jsonData = [[NSMutableData alloc] init];
+ }
+ return self;
+}
+
+// Begin the authentication and authorization process. Begin the process by
+// creating an oAuth2 request to google api's including the needed scopes to
+// fetch the users host list.
+- (void)refreshHostList:(GTMOAuth2Authentication*)authReq
+ delegate:(id<HostRefreshDelegate>)delegate {
+
+ CHECK(_delegate == nil); // Do not reuse an instance of this class
+
+ _delegate = delegate;
+
+ [Authorize beginRequest:authReq
+ delegate:self
+ didFinishSelector:@selector(authentication:request:error:)];
+}
+
+// Handle completion of the authorization process. Append service credentials
+// for jabber. If an error occurred, notify user.
+- (void)authentication:(NSObject*)auth
+ request:(NSMutableURLRequest*)request
+ error:(NSError*)error {
+ if (error != nil) {
+ [self formatErrorMessage:error.localizedDescription];
+ } else {
+ // Add credentials for service
+ [Authorize appendCredentials:request];
+
+ // Begin connection, the returned reference is not useful right now and
+ // marked as __unused
+ __unused NSURLConnection* connection =
+ [[NSURLConnection alloc] initWithRequest:request delegate:self];
+ }
+}
+
+// @protocol NSURLConnectionDelegate, handle any error during connection
+- (void)connection:(NSURLConnection*)connection
+ didFailWithError:(NSError*)error {
+ [self formatErrorMessage:[error localizedDescription]];
+
+ [self notifyDelegate];
+}
+
+// @protocol NSURLConnectionDataDelegate, may be called async multiple times.
+// Each call appends the new data to the known data until completed.
+- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
+ [_jsonData appendData:data];
+}
+
+// @protocol NSURLConnectionDataDelegate
+// Ensure connection succeeded: HTTP 200 OK
+- (void)connection:(NSURLConnection*)connection
+ didReceiveResponse:(NSURLResponse*)response {
+ NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
+ if ([response respondsToSelector:@selector(allHeaderFields)]) {
+ NSNumber* responseCode =
+ [[NSNumber alloc] initWithInteger:[httpResponse statusCode]];
+ if (responseCode.intValue != 200) {
+ [self formatErrorMessage:[NSString
+ stringWithFormat:@"HTTP STATUS CODE: %d",
+ [httpResponse statusCode]]];
+ }
+ }
+}
+
+// @protocol NSURLConnectionDataDelegate handle a completed connection, parse
+// received data, and return host list to delegate
+- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
+ [self notifyDelegate];
+}
+
+// Store a formatted error message to return later
+- (void)formatErrorMessage:(NSString*)error {
+ _errorMessage = kDefaultErrorMessage;
+ if (error != nil && error.length > 0) {
+ _errorMessage = [_errorMessage
+ stringByAppendingString:[@" " stringByAppendingString:error]];
+ }
+}
+
+// The connection has finished, call to delegate
+- (void)notifyDelegate {
+ if (_jsonData.length == 0 && _errorMessage == nil) {
+ [self formatErrorMessage:nil];
+ }
+
+ [_delegate hostListRefresh:[Host parseListFromJSON:_jsonData]
+ errorMessage:_errorMessage];
+}
+@end
diff --git a/remoting/ios/host_refresh_test_helper.h b/remoting/ios/host_refresh_test_helper.h
new file mode 100644
index 0000000000..aac365b31a
--- /dev/null
+++ b/remoting/ios/host_refresh_test_helper.h
@@ -0,0 +1,102 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_HOST_REFRESH_TEST_HELPER_H_
+#define REMOTING_IOS_HOST_REFRESH_TEST_HELPER_H_
+
+#import <Foundation/Foundation.h>
+
+namespace remoting {
+
+class HostRefreshTestHelper {
+ public:
+ constexpr static NSString* CloseTag = @"\",";
+
+ constexpr static NSString* CreatedTimeTag = @"\"createdTime\":\"";
+ constexpr static NSString* HostIdTag = @"\"hostId\":\"";
+ constexpr static NSString* HostNameTag = @"\"hostName\":\"";
+ constexpr static NSString* HostVersionTag = @"\"hostVersion\":\"";
+ constexpr static NSString* KindTag = @"\"kind\":\"";
+ constexpr static NSString* JabberIdTag = @"\"jabberId\":\"";
+ constexpr static NSString* PublicKeyTag = @"\"publicKey\":\"";
+ constexpr static NSString* StatusTag = @"\"status\":\"";
+ constexpr static NSString* UpdatedTimeTag = @"\"updatedTime\":\"";
+
+ constexpr static NSString* CreatedTimeTest = @"2000-01-01T00:00:01.000Z";
+ constexpr static NSString* HostIdTest = @"Host1";
+ constexpr static NSString* HostNameTest = @"HostName1";
+ constexpr static NSString* HostVersionTest = @"2.22.5.4";
+ constexpr static NSString* KindTest = @"chromoting#host";
+ constexpr static NSString* JabberIdTest = @"JabberingOn";
+ constexpr static NSString* PublicKeyTest = @"AAAAABBBBBZZZZZ";
+ constexpr static NSString* StatusTest = @"TESTING";
+ constexpr static NSString* UpdatedTimeTest = @"2004-01-01T00:00:01.000Z";
+
+ static NSMutableData* GetHostList(int numHosts) {
+ return [NSMutableData
+ dataWithData:[GetMultipleHosts(numHosts)
+ dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ static NSMutableData* GetHostList(NSString* hostList) {
+ return [NSMutableData
+ dataWithData:[hostList dataUsingEncoding:NSUTF8StringEncoding]];
+ }
+
+ static NSString* GetMultipleHosts(int numHosts) {
+ NSString* client = [NSString
+ stringWithFormat:
+ @"%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@%@",
+ @"{",
+ CreatedTimeTag,
+ CreatedTimeTest,
+ CloseTag,
+ HostIdTag,
+ HostIdTest,
+ CloseTag,
+ HostNameTag,
+ HostNameTest,
+ CloseTag,
+ HostNameTag,
+ HostNameTest,
+ CloseTag,
+ HostVersionTag,
+ HostVersionTest,
+ CloseTag,
+ KindTag,
+ KindTest,
+ CloseTag,
+ JabberIdTag,
+ JabberIdTest,
+ CloseTag,
+ PublicKeyTag,
+ PublicKeyTest,
+ CloseTag,
+ StatusTag,
+ StatusTest,
+ CloseTag,
+ UpdatedTimeTag,
+ UpdatedTimeTest,
+ @"\"}"];
+
+ NSMutableString* hostList = [NSMutableString
+ stringWithString:
+ @"{\"data\":{\"kind\":\"chromoting#hostList\",\"items\":["];
+
+ for (int i = 0; i < numHosts; i++) {
+ [hostList appendString:client];
+ if (i < numHosts - 1) {
+ [hostList appendString:@","]; // common separated
+ }
+ }
+
+ [hostList appendString:@"]}}"];
+
+ return [hostList copy];
+ }
+};
+
+} // namespace remoting
+
+#endif // REMOTING_IOS_HOST_REFRESH_TEST_HELPER_H_ \ No newline at end of file
diff --git a/remoting/ios/host_refresh_unittest.mm b/remoting/ios/host_refresh_unittest.mm
new file mode 100644
index 0000000000..54f2ee7c26
--- /dev/null
+++ b/remoting/ios/host_refresh_unittest.mm
@@ -0,0 +1,170 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/host_refresh.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+#import "remoting/ios/host.h"
+#import "remoting/ios/host_refresh_test_helper.h"
+
+@interface HostRefreshDelegateTester : NSObject<HostRefreshDelegate>
+
+@property(nonatomic) NSArray* hostList;
+@property(nonatomic) NSString* errorMessage;
+
+@end
+
+@implementation HostRefreshDelegateTester
+
+@synthesize hostList = _hostList;
+@synthesize errorMessage = _errorMessage;
+
+- (void)hostListRefresh:(NSArray*)hostList
+ errorMessage:(NSString*)errorMessage {
+ _hostList = hostList;
+ _errorMessage = errorMessage;
+}
+
+- (bool)receivedHosts {
+ return (_hostList.count > 0);
+}
+
+- (bool)receivedErrorMessage {
+ return (_errorMessage != nil);
+}
+
+@end
+
+namespace remoting {
+
+namespace {
+
+NSString* kErrorMessageTest = @"TestErrorMessage";
+
+} // namespace
+
+class HostRefreshTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ hostRefreshProcessor_ = [[HostRefresh allocWithZone:nil] init];
+ delegateTester_ = [[HostRefreshDelegateTester alloc] init];
+ [hostRefreshProcessor_ setDelegate:delegateTester_];
+ }
+
+ void CreateHostList(int numHosts) {
+ [hostRefreshProcessor_
+ setJsonData:HostRefreshTestHelper::GetHostList(numHosts)];
+ }
+
+ NSError* CreateErrorFromString(NSString* message) {
+ NSDictionary* errorDictionary = nil;
+
+ if (message != nil) {
+ errorDictionary = @{NSLocalizedDescriptionKey : message};
+ }
+
+ return [[NSError alloc] initWithDomain:@"HostRefreshTest"
+ code:EPERM
+ userInfo:errorDictionary];
+ }
+
+ HostRefresh* hostRefreshProcessor_;
+ HostRefreshDelegateTester* delegateTester_;
+};
+
+TEST_F(HostRefreshTest, ErrorFormatter) {
+ [hostRefreshProcessor_ connection:nil
+ didFailWithError:CreateErrorFromString(nil)];
+ ASSERT_FALSE(hostRefreshProcessor_.errorMessage == nil);
+
+ [hostRefreshProcessor_ connection:nil
+ didFailWithError:CreateErrorFromString(@"")];
+ ASSERT_FALSE(hostRefreshProcessor_.errorMessage == nil);
+
+ [hostRefreshProcessor_ connection:nil
+ didFailWithError:CreateErrorFromString(kErrorMessageTest)];
+ ASSERT_TRUE([hostRefreshProcessor_.errorMessage
+ rangeOfString:kErrorMessageTest].location != NSNotFound);
+}
+
+TEST_F(HostRefreshTest, JSONParsing) {
+ // There were no hosts returned
+ CreateHostList(0);
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE(delegateTester_.hostList.count == 0);
+
+ CreateHostList(1);
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE(delegateTester_.hostList.count == 1);
+
+ Host* host = static_cast<Host*>([delegateTester_.hostList objectAtIndex:0]);
+ ASSERT_NSEQ(HostRefreshTestHelper::CreatedTimeTest, host.createdTime);
+ ASSERT_NSEQ(HostRefreshTestHelper::HostIdTest, host.hostId);
+ ASSERT_NSEQ(HostRefreshTestHelper::HostNameTest, host.hostName);
+ ASSERT_NSEQ(HostRefreshTestHelper::HostVersionTest, host.hostVersion);
+ ASSERT_NSEQ(HostRefreshTestHelper::KindTest, host.kind);
+ ASSERT_NSEQ(HostRefreshTestHelper::JabberIdTest, host.jabberId);
+ ASSERT_NSEQ(HostRefreshTestHelper::PublicKeyTest, host.publicKey);
+ ASSERT_NSEQ(HostRefreshTestHelper::StatusTest, host.status);
+ ASSERT_NSEQ(HostRefreshTestHelper::UpdatedTimeTest, host.updatedTime);
+
+ CreateHostList(11);
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE(delegateTester_.hostList.count == 11);
+
+ // An error in parsing returns no hosts
+ [hostRefreshProcessor_
+ setJsonData:
+ [NSMutableData
+ dataWithData:
+ [@"{\"dataaaaaafa\":{\"kiffffnd\":\"chromoting#hostList\"}}"
+ dataUsingEncoding:NSUTF8StringEncoding]]];
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE(delegateTester_.hostList.count == 0);
+}
+
+TEST_F(HostRefreshTest, HostListDelegateNoList) {
+ // Hosts were not processed at all
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_FALSE([delegateTester_ receivedHosts]);
+ ASSERT_TRUE([delegateTester_ receivedErrorMessage]);
+
+ // There were no hosts returned
+ CreateHostList(0);
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_FALSE([delegateTester_ receivedHosts]);
+ ASSERT_TRUE([delegateTester_ receivedErrorMessage]);
+}
+
+TEST_F(HostRefreshTest, HostListDelegateHasList) {
+ CreateHostList(1);
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE([delegateTester_ receivedHosts]);
+ ASSERT_FALSE([delegateTester_ receivedErrorMessage]);
+}
+
+TEST_F(HostRefreshTest, HostListDelegateHasListWithError) {
+ CreateHostList(1);
+
+ [hostRefreshProcessor_ connection:nil
+ didFailWithError:CreateErrorFromString(kErrorMessageTest)];
+
+ [hostRefreshProcessor_ connectionDidFinishLoading:nil];
+ ASSERT_TRUE([delegateTester_ receivedHosts]);
+ ASSERT_TRUE([delegateTester_ receivedErrorMessage]);
+}
+
+TEST_F(HostRefreshTest, ConnectionFailed) {
+ [hostRefreshProcessor_ connection:nil didFailWithError:nil];
+ ASSERT_FALSE([delegateTester_ receivedHosts]);
+ ASSERT_TRUE([delegateTester_ receivedErrorMessage]);
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/key_input.h b/remoting/ios/key_input.h
new file mode 100644
index 0000000000..302a63cd28
--- /dev/null
+++ b/remoting/ios/key_input.h
@@ -0,0 +1,35 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_KEY_INPUT_H_
+#define REMOTING_IOS_KEY_INPUT_H_
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+// Key codes are translated from the on screen keyboard to the scan codes
+// needed for Chromoting input. We don't have a good automated approach to do
+// this. Instead we have created a mapping manually via trial and error. To
+// support other keyboards in this context we would have to test and create a
+// mapping for each keyboard manually.
+
+// Contract to handle translated key presses from the on-screen keyboard to
+// the format required for Chromoting keyboard input
+@protocol KeyInputDelegate<NSObject>
+
+- (void)keyboardActionKeyCode:(uint32_t)keyPressed isKeyDown:(BOOL)keyDown;
+
+- (void)keyboardDismissed;
+
+@end
+
+@interface KeyInput : UIView<UIKeyInput>
+
+@property(weak, nonatomic) id<KeyInputDelegate> delegate;
+
+- (void)ctrlAltDel;
+
+@end
+
+#endif // REMOTING_IOS_KEY_INPUT_H_ \ No newline at end of file
diff --git a/remoting/ios/key_input.mm b/remoting/ios/key_input.mm
new file mode 100644
index 0000000000..0e51efa7ac
--- /dev/null
+++ b/remoting/ios/key_input.mm
@@ -0,0 +1,111 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/key_input.h"
+#import "remoting/ios/key_map_us.h"
+
+@interface KeyInput (Private)
+- (void)transmitAppropriateKeyCode:(NSString*)text;
+- (void)transmitKeyCode:(NSInteger)keyCode needShift:(bool)needShift;
+@end
+
+@implementation KeyInput
+
+@synthesize delegate = _delegate;
+
+// Override UIKeyInput::UITextInputTraits property
+- (UIKeyboardType)keyboardType {
+ return UIKeyboardTypeAlphabet;
+}
+
+// Override UIView::UIResponder, when this interface is the first responder
+// on-screen keyboard input will create events for Chromoting keyboard input
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+// Override UIView::UIResponder
+// Keyboard was dismissed
+- (BOOL)resignFirstResponder {
+ BOOL wasFirstResponder = self.isFirstResponder;
+ BOOL didResignFirstReponder =
+ [super resignFirstResponder]; // I'm not sure that this returns YES when
+ // first responder was resigned, but for
+ // now I don't actually need to know what
+ // the return from super means.
+ if (wasFirstResponder) {
+ [_delegate keyboardDismissed];
+ }
+
+ return didResignFirstReponder;
+}
+
+// @protocol UIKeyInput, Send backspace
+- (void)deleteBackward {
+ [self transmitKeyCode:kKeyCodeUS[kBackspaceIndex] needShift:false];
+}
+
+// @protocol UIKeyInput, Assume this is a text input
+- (BOOL)hasText {
+ return YES;
+}
+
+// @protocol UIKeyInput, Translate inserted text to key presses, one char at a
+// time
+- (void)insertText:(NSString*)text {
+ [self transmitAppropriateKeyCode:text];
+}
+
+- (void)ctrlAltDel {
+ if (_delegate) {
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kCtrlIndex] isKeyDown:YES];
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kAltIndex] isKeyDown:YES];
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kDelIndex] isKeyDown:YES];
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kDelIndex] isKeyDown:NO];
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kAltIndex] isKeyDown:NO];
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kCtrlIndex] isKeyDown:NO];
+ }
+}
+
+// When inserting multiple characters, process them one at a time. |text| is as
+// it was output on the device. The shift key is not naturally presented in the
+// input stream, and must be inserted by inspecting each char and considering
+// that if the key was input on a traditional keyboard that the character would
+// have required a shift. Assume caps lock does not exist.
+- (void)transmitAppropriateKeyCode:(NSString*)text {
+ for (int i = 0; i < [text length]; ++i) {
+ NSInteger charToSend = [text characterAtIndex:i];
+
+ if (charToSend <= kKeyboardKeyMaxUS) {
+ [self transmitKeyCode:kKeyCodeUS[charToSend]
+ needShift:kIsShiftRequiredUS[charToSend]];
+ }
+ }
+}
+
+// |charToSend| is as it was output on the device. Some call this a
+// 'key press'. For Chromoting this must be transferred as a key down (press
+// down with a finger), followed by a key up (finger is removed from the
+// keyboard)
+//
+// The delivery may be an upper case or special character. Chromoting is just
+// interested in the button that was pushed, so to create an upper case
+// character, first send a shift press, then the button, then release shift
+- (void)transmitKeyCode:(NSInteger)keyCode needShift:(bool)needShift {
+ if (keyCode > 0 && _delegate) {
+ if (needShift) {
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kShiftIndex] isKeyDown:YES];
+ }
+ [_delegate keyboardActionKeyCode:keyCode isKeyDown:YES];
+ [_delegate keyboardActionKeyCode:keyCode isKeyDown:NO];
+ if (needShift) {
+ [_delegate keyboardActionKeyCode:kKeyCodeUS[kShiftIndex] isKeyDown:NO];
+ }
+ }
+}
+@end
diff --git a/remoting/ios/key_input_unittest.mm b/remoting/ios/key_input_unittest.mm
new file mode 100644
index 0000000000..b893b9f94d
--- /dev/null
+++ b/remoting/ios/key_input_unittest.mm
@@ -0,0 +1,124 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/key_input.h"
+#import "remoting/ios/key_map_us.h"
+
+#include <vector>
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+@interface KeyInputDelegateTester : NSObject<KeyInputDelegate> {
+ @private
+ std::vector<uint32_t> _keyList;
+}
+
+@property(nonatomic, assign) int numKeysDown;
+@property(nonatomic, assign) BOOL wasDismissed;
+
+- (std::vector<uint32_t>&)getKeyList;
+
+@end
+
+@implementation KeyInputDelegateTester
+
+- (std::vector<uint32_t>&)getKeyList {
+ return _keyList;
+}
+
+- (void)keyboardDismissed {
+ // This can not be tested, because we can not set |keyInput_| as
+ // FirstResponder in this test harness
+ _wasDismissed = true;
+}
+
+- (void)keyboardActionKeyCode:(uint32_t)keyPressed isKeyDown:(BOOL)keyDown {
+ if (keyDown) {
+ _keyList.push_back(keyPressed);
+ _numKeysDown++;
+ } else {
+ _numKeysDown--;
+ }
+}
+
+@end
+
+namespace remoting {
+
+class KeyInputTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ keyInput_ = [[KeyInput allocWithZone:nil] init];
+ delegateTester_ = [[KeyInputDelegateTester alloc] init];
+ keyInput_.delegate = delegateTester_;
+ }
+
+ KeyInput* keyInput_;
+ KeyInputDelegateTester* delegateTester_;
+};
+
+TEST_F(KeyInputTest, SendKey) {
+ // Empty
+ [keyInput_ insertText:@""];
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(0, [delegateTester_ getKeyList].size());
+
+ // Value is out of bounds
+ [keyInput_ insertText:@"ó"];
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(0, [delegateTester_ getKeyList].size());
+
+ // Lower case
+ [keyInput_ insertText:@"a"];
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(1, [delegateTester_ getKeyList].size());
+ ASSERT_EQ(kKeyCodeUS['a'], [delegateTester_ getKeyList][0]);
+ // Upper Case
+ [delegateTester_ getKeyList].clear();
+ [keyInput_ insertText:@"A"];
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(2, [delegateTester_ getKeyList].size());
+ ASSERT_EQ(kKeyCodeUS[kShiftIndex], [delegateTester_ getKeyList][0]);
+ ASSERT_EQ(kKeyCodeUS['A'], [delegateTester_ getKeyList][1]);
+
+ // Multiple characters and mixed case
+ [delegateTester_ getKeyList].clear();
+ [keyInput_ insertText:@"ABCabc"];
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(9, [delegateTester_ getKeyList].size());
+ ASSERT_EQ(kKeyCodeUS[kShiftIndex], [delegateTester_ getKeyList][0]);
+ ASSERT_EQ(kKeyCodeUS['A'], [delegateTester_ getKeyList][1]);
+ ASSERT_EQ(kKeyCodeUS[kShiftIndex], [delegateTester_ getKeyList][2]);
+ ASSERT_EQ(kKeyCodeUS['B'], [delegateTester_ getKeyList][3]);
+ ASSERT_EQ(kKeyCodeUS[kShiftIndex], [delegateTester_ getKeyList][4]);
+ ASSERT_EQ(kKeyCodeUS['C'], [delegateTester_ getKeyList][5]);
+ ASSERT_EQ(kKeyCodeUS['a'], [delegateTester_ getKeyList][6]);
+ ASSERT_EQ(kKeyCodeUS['b'], [delegateTester_ getKeyList][7]);
+ ASSERT_EQ(kKeyCodeUS['c'], [delegateTester_ getKeyList][8]);
+}
+
+TEST_F(KeyInputTest, CtrlAltDel) {
+ [keyInput_ ctrlAltDel];
+
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(3, [delegateTester_ getKeyList].size());
+ ASSERT_EQ(kKeyCodeUS[kCtrlIndex], [delegateTester_ getKeyList][0]);
+ ASSERT_EQ(kKeyCodeUS[kAltIndex], [delegateTester_ getKeyList][1]);
+ ASSERT_EQ(kKeyCodeUS[kDelIndex], [delegateTester_ getKeyList][2]);
+}
+
+TEST_F(KeyInputTest, Backspace) {
+ [keyInput_ deleteBackward];
+
+ ASSERT_EQ(0, delegateTester_.numKeysDown);
+ ASSERT_EQ(1, [delegateTester_ getKeyList].size());
+ ASSERT_EQ(kKeyCodeUS[kBackspaceIndex], [delegateTester_ getKeyList][0]);
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/key_map_us.h b/remoting/ios/key_map_us.h
new file mode 100644
index 0000000000..c8283f06ff
--- /dev/null
+++ b/remoting/ios/key_map_us.h
@@ -0,0 +1,288 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_KEY_MAP_US_H_
+#define REMOTING_IOS_KEY_MAP_US_H_
+
+// A mapping for the US keyboard on a US IPAD to Chromoting Scancodes
+
+// This must be less than or equal to the size of
+// kIsShiftRequiredUS and kKeyCodeUS.
+const int kKeyboardKeyMaxUS = 126;
+
+// Index for specific keys
+const uint32_t kShiftIndex = 128;
+const uint32_t kBackspaceIndex = 129;
+const uint32_t kCtrlIndex = 130;
+const uint32_t kAltIndex = 131;
+const uint32_t kDelIndex = 132;
+
+const BOOL kIsShiftRequiredUS[] = {
+ NO, // [0] Numbering fields by index, not by count
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, // [10] ENTER
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, // [20]
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, //
+ NO, // [30]
+ NO, //
+ NO, // SPACE
+ YES, // !
+ YES, // "
+ YES, // #
+ YES, // $
+ YES, // %
+ YES, // &
+ NO, // '
+ YES, // [40] (
+ YES, // )
+ YES, // *
+ YES, // +
+ NO, // ,
+ NO, // -
+ NO, // .
+ NO, // /
+ NO, // 0
+ NO, // 1
+ NO, // [50] 2
+ NO, // 3
+ NO, // 4
+ NO, // 5
+ NO, // 6
+ NO, // 7
+ NO, // 8
+ NO, // 9
+ YES, // :
+ NO, // ;
+ YES, // [60] <
+ NO, // =
+ YES, // >
+ YES, // ?
+ YES, // @
+ YES, // A
+ YES, // B
+ YES, // C
+ YES, // D
+ YES, // E
+ YES, // [70] F
+ YES, // G
+ YES, // H
+ YES, // I
+ YES, // J
+ YES, // K
+ YES, // L
+ YES, // M
+ YES, // N
+ YES, // O
+ YES, // [80] P
+ YES, // Q
+ YES, // R
+ YES, // S
+ YES, // T
+ YES, // U
+ YES, // V
+ YES, // W
+ YES, // X
+ YES, // Y
+ YES, // [90] Z
+ NO, // [
+ NO, // BACKSLASH
+ NO, // ]
+ YES, // ^
+ YES, // _
+ NO, //
+ NO, // a
+ NO, // b
+ NO, // c
+ NO, // [100] d
+ NO, // e
+ NO, // f
+ NO, // g
+ NO, // h
+ NO, // i
+ NO, // j
+ NO, // k
+ NO, // l
+ NO, // m
+ NO, // [110] n
+ NO, // o
+ NO, // p
+ NO, // q
+ NO, // r
+ NO, // s
+ NO, // t
+ NO, // u
+ NO, // v
+ NO, // w
+ NO, // [120] x
+ NO, // y
+ NO, // z
+ YES, // {
+ YES, // |
+ YES, // }
+ YES, // ~
+ NO // [127]
+};
+
+const uint32_t kKeyCodeUS[] = {
+ 0, // [0] Numbering fields by index, not by count
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0x070028, // [10] ENTER
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, // [20]
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, //
+ 0, // [30]
+ 0, //
+ 0x07002c, // SPACE
+ 0x07001e, // !
+ 0x070034, // "
+ 0x070020, // #
+ 0x070021, // $
+ 0x070022, // %
+ 0x070024, // &
+ 0x070034, // '
+ 0x070026, // [40] (
+ 0x070027, // )
+ 0x070025, // *
+ 0x07002e, // +
+ 0x070036, // ,
+ 0x07002d, // -
+ 0x070037, // .
+ 0x070038, // /
+ 0x070027, // 0
+ 0x07001e, // 1
+ 0x07001f, // [50] 2
+ 0x070020, // 3
+ 0x070021, // 4
+ 0x070022, // 5
+ 0x070023, // 6
+ 0x070024, // 7
+ 0x070025, // 8
+ 0x070026, // 9
+ 0x070033, // :
+ 0x070033, // ;
+ 0x070036, // [60] <
+ 0x07002e, // =
+ 0x070037, // >
+ 0x070038, // ?
+ 0x07001f, // @
+ 0x070004, // A
+ 0x070005, // B
+ 0x070006, // C
+ 0x070007, // D
+ 0x070008, // E
+ 0x070009, // [70] F
+ 0x07000a, // G
+ 0x07000b, // H
+ 0x07000c, // I
+ 0x07000d, // J
+ 0x07000e, // K
+ 0x07000f, // L
+ 0x070010, // M
+ 0x070011, // N
+ 0x070012, // O
+ 0x070013, // [80] P
+ 0x070014, // Q
+ 0x070015, // R
+ 0x070016, // S
+ 0x070017, // T
+ 0x070018, // U
+ 0x070019, // V
+ 0x07001a, // W
+ 0x07001b, // X
+ 0x07001c, // Y
+ 0x07001d, // [90] Z
+ 0x07002f, // [
+ 0x070031, // BACKSLASH
+ 0x070030, // ]
+ 0x070023, // ^
+ 0x07002d, // _
+ 0, //
+ 0x070004, // a
+ 0x070005, // b
+ 0x070006, // c
+ 0x070007, // [100] d
+ 0x070008, // e
+ 0x070009, // f
+ 0x07000a, // g
+ 0x07000b, // h
+ 0x07000c, // i
+ 0x07000d, // j
+ 0x07000e, // k
+ 0x07000f, // l
+ 0x070010, // m
+ 0x070011, // [110] n
+ 0x070012, // o
+ 0x070013, // p
+ 0x070014, // q
+ 0x070015, // r
+ 0x070016, // s
+ 0x070017, // t
+ 0x070018, // u
+ 0x070019, // v
+ 0x07001a, // w
+ 0x07001b, // [120] x
+ 0x07001c, // y
+ 0x07001d, // z
+ 0x07002f, // {
+ 0x070031, // |
+ 0x070030, // }
+ 0x070035, // ~
+ 0, // [127]
+ 0x0700e1, // SHIFT
+ 0x07002a, // BACKSPACE
+ 0x0700e0, // CTRL
+ 0x0700e2, // ALT
+ 0x07004c, // DEL
+};
+
+#endif // REMOTING_IOS_KEY_MAP_US_H_
diff --git a/remoting/ios/ui/cursor_texture.h b/remoting/ios/ui/cursor_texture.h
new file mode 100644
index 0000000000..398a42467b
--- /dev/null
+++ b/remoting/ios/ui/cursor_texture.h
@@ -0,0 +1,58 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_CURSOR_TEXTURE_H_
+#define REMOTING_IOS_UI_CURSOR_TEXTURE_H_
+
+#import <Foundation/Foundation.h>
+#import <GLKit/GLKit.h>
+
+#import "base/memory/scoped_ptr.h"
+
+#import "remoting/ios/utility.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+#include "third_party/webrtc/modules/desktop_capture/mouse_cursor.h"
+
+@interface CursorTexture : NSObject {
+ @private
+ // GL name
+ GLuint _textureId;
+ webrtc::DesktopSize _textureSize;
+ BOOL _needInitialize;
+
+ // The current cursor
+ scoped_ptr<webrtc::MouseCursor> _cursor;
+
+ BOOL _needCursorRedraw;
+
+ // Rectangle of the most recent cursor drawn to a GL Texture. On each
+ // successive frame when a new cursor is available this region is cleared on
+ // the GL Texture, so that the GL Texture is completely transparent again, and
+ // the cursor is then redrawn.
+ webrtc::DesktopRect _cursorDrawnToGL;
+}
+
+- (const webrtc::DesktopSize&)textureSize;
+
+- (void)setTextureSize:(const webrtc::DesktopSize&)size;
+
+- (const webrtc::MouseCursor&)cursor;
+
+- (void)setCursor:(webrtc::MouseCursor*)cursor;
+
+// bind this object to an effect's via the effects properties
+- (void)bindToEffect:(GLKEffectPropertyTexture*)effectProperty;
+
+// True if the cursor has changed in a way that requires it to be redrawn
+- (BOOL)needDrawAtPosition:(const webrtc::DesktopVector&)position;
+
+// needDrawAtPosition must be checked prior to calling drawWithMousePosition.
+// Draw mouse at the new position.
+- (void)drawWithMousePosition:(const webrtc::DesktopVector&)position;
+
+- (void)releaseTexture;
+
+@end
+
+#endif // REMOTING_IOS_UI_CURSOR_TEXTURE_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/cursor_texture.mm b/remoting/ios/ui/cursor_texture.mm
new file mode 100644
index 0000000000..9ffa5f7f12
--- /dev/null
+++ b/remoting/ios/ui/cursor_texture.mm
@@ -0,0 +1,181 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/cursor_texture.h"
+
+@implementation CursorTexture
+
+- (id)init {
+ self = [super init];
+ if (self) {
+ _needCursorRedraw = NO;
+ _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(0, 0, 0, 0);
+ }
+ return self;
+}
+
+- (const webrtc::DesktopSize&)textureSize {
+ return _textureSize;
+}
+
+- (void)setTextureSize:(const webrtc::DesktopSize&)size {
+ if (!_textureSize.equals(size)) {
+ _textureSize.set(size.width(), size.height());
+ _needInitialize = true;
+ }
+}
+
+- (const webrtc::MouseCursor&)cursor {
+ return *_cursor.get();
+}
+
+- (void)setCursor:(webrtc::MouseCursor*)cursor {
+ _cursor.reset(cursor);
+
+ if (_cursor.get() != NULL && _cursor->image().data()) {
+ _needCursorRedraw = true;
+ }
+}
+
+- (void)bindToEffect:(GLKEffectPropertyTexture*)effectProperty {
+ glGenTextures(1, &_textureId);
+ [Utility bindTextureForIOS:_textureId];
+
+ // This is the Cursor layer, and is stamped on top of Desktop as a
+ // transparent image
+ effectProperty.target = GLKTextureTarget2D;
+ effectProperty.name = _textureId;
+ effectProperty.envMode = GLKTextureEnvModeDecal;
+ effectProperty.enabled = GL_TRUE;
+
+ [Utility logGLErrorCode:@"CursorTexture bindToTexture"];
+ // Release context
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+- (BOOL)needDrawAtPosition:(const webrtc::DesktopVector&)position {
+ return (_cursor.get() != NULL &&
+ (_needInitialize || _needCursorRedraw == YES ||
+ _cursorDrawnToGL.left() != position.x() - _cursor->hotspot().x() ||
+ _cursorDrawnToGL.top() != position.y() - _cursor->hotspot().y()));
+}
+
+- (void)drawWithMousePosition:(const webrtc::DesktopVector&)position {
+ if (_textureSize.height() == 0 && _textureSize.width() == 0) {
+ return;
+ }
+
+ [Utility bindTextureForIOS:_textureId];
+
+ if (_needInitialize) {
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ _textureSize.width(),
+ _textureSize.height(),
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ NULL);
+
+ [Utility logGLErrorCode:@"CursorTexture initializeTextureSurfaceWithSize"];
+ _needInitialize = false;
+ }
+ // When the cursor needs to be redraw in a different spot then we must clear
+ // the previous area.
+
+ DCHECK([self needDrawAtPosition:position]);
+
+ if (_cursorDrawnToGL.width() > 0 && _cursorDrawnToGL.height() > 0) {
+ webrtc::BasicDesktopFrame transparentCursor(_cursorDrawnToGL.size());
+
+ if (transparentCursor.data() != NULL) {
+ DCHECK(transparentCursor.kBytesPerPixel ==
+ _cursor->image().kBytesPerPixel);
+ memset(transparentCursor.data(),
+ 0,
+ transparentCursor.stride() * transparentCursor.size().height());
+
+ [Utility drawSubRectToGLFromRectOfSize:_textureSize
+ subRect:_cursorDrawnToGL
+ data:transparentCursor.data()];
+
+ // there is no longer any cursor drawn to screen
+ _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(0, 0, 0, 0);
+ }
+ }
+
+ if (_cursor.get() != NULL) {
+
+ CGRect screen =
+ CGRectMake(0.0, 0.0, _textureSize.width(), _textureSize.height());
+ CGRect cursor = CGRectMake(position.x() - _cursor->hotspot().x(),
+ position.y() - _cursor->hotspot().y(),
+ _cursor->image().size().width(),
+ _cursor->image().size().height());
+
+ if (CGRectContainsRect(screen, cursor)) {
+ _cursorDrawnToGL = webrtc::DesktopRect::MakeXYWH(cursor.origin.x,
+ cursor.origin.y,
+ cursor.size.width,
+ cursor.size.height);
+
+ [Utility drawSubRectToGLFromRectOfSize:_textureSize
+ subRect:_cursorDrawnToGL
+ data:_cursor->image().data()];
+
+ } else if (CGRectIntersectsRect(screen, cursor)) {
+ // Some of the cursor falls off screen, need to clip it
+ CGRect intersection = CGRectIntersection(screen, cursor);
+ _cursorDrawnToGL =
+ webrtc::DesktopRect::MakeXYWH(intersection.origin.x,
+ intersection.origin.y,
+ intersection.size.width,
+ intersection.size.height);
+
+ webrtc::BasicDesktopFrame partialCursor(_cursorDrawnToGL.size());
+
+ if (partialCursor.data()) {
+ DCHECK(partialCursor.kBytesPerPixel == _cursor->image().kBytesPerPixel);
+
+ uint32_t src_stride = _cursor->image().stride();
+ uint32_t dst_stride = partialCursor.stride();
+
+ uint8_t* source = _cursor->image().data();
+ source += abs((static_cast<int32_t>(cursor.origin.y) -
+ _cursorDrawnToGL.top())) *
+ src_stride;
+ source += abs((static_cast<int32_t>(cursor.origin.x) -
+ _cursorDrawnToGL.left())) *
+ _cursor->image().kBytesPerPixel;
+ uint8_t* dst = partialCursor.data();
+
+ for (uint32_t y = 0; y < _cursorDrawnToGL.height(); y++) {
+ memcpy(dst, source, dst_stride);
+ source += src_stride;
+ dst += dst_stride;
+ }
+
+ [Utility drawSubRectToGLFromRectOfSize:_textureSize
+ subRect:_cursorDrawnToGL
+ data:partialCursor.data()];
+ }
+ }
+ }
+
+ _needCursorRedraw = false;
+ [Utility logGLErrorCode:@"CursorTexture drawWithMousePosition"];
+ // Release context
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+- (void)releaseTexture {
+ glDeleteTextures(1, &_textureId);
+}
+
+@end
diff --git a/remoting/ios/ui/desktop_texture.h b/remoting/ios/ui/desktop_texture.h
new file mode 100644
index 0000000000..28a330c509
--- /dev/null
+++ b/remoting/ios/ui/desktop_texture.h
@@ -0,0 +1,38 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_DESKTOP_TEXTURE_H_
+#define REMOTING_IOS_UI_DESKTOP_TEXTURE_H_
+
+#import <Foundation/Foundation.h>
+#import <GLKit/GLKit.h>
+
+#import "remoting/ios/utility.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+@interface DesktopTexture : NSObject {
+ @private
+ // GL name
+ GLuint _textureId;
+ webrtc::DesktopSize _textureSize;
+ BOOL _needInitialize;
+}
+
+- (const webrtc::DesktopSize&)textureSize;
+
+- (void)setTextureSize:(const webrtc::DesktopSize&)size;
+
+// bind this object to an effect's via the effects properties
+- (void)bindToEffect:(GLKEffectPropertyTexture*)effectProperty;
+
+- (BOOL)needDraw;
+
+// draw a region of the texture
+- (void)drawRegion:(GLRegion*)region rect:(CGRect)rect;
+
+- (void)releaseTexture;
+
+@end
+
+#endif // REMOTING_IOS_UI_DESKTOP_TEXTURE_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/desktop_texture.mm b/remoting/ios/ui/desktop_texture.mm
new file mode 100644
index 0000000000..d806dee8d7
--- /dev/null
+++ b/remoting/ios/ui/desktop_texture.mm
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/desktop_texture.h"
+
+@implementation DesktopTexture
+
+- (const webrtc::DesktopSize&)textureSize {
+ return _textureSize;
+}
+
+- (void)setTextureSize:(const webrtc::DesktopSize&)size {
+ if (!_textureSize.equals(size)) {
+ _textureSize.set(size.width(), size.height());
+ _needInitialize = true;
+ }
+}
+
+- (void)bindToEffect:(GLKEffectPropertyTexture*)effectProperty {
+ glGenTextures(1, &_textureId);
+ [Utility bindTextureForIOS:_textureId];
+
+ // This is the HOST Desktop layer, and each draw will always replace what is
+ // currently in the draw context
+ effectProperty.target = GLKTextureTarget2D;
+ effectProperty.name = _textureId;
+ effectProperty.envMode = GLKTextureEnvModeReplace;
+ effectProperty.enabled = GL_TRUE;
+
+ [Utility logGLErrorCode:@"DesktopTexture bindToTexture"];
+ // Release context
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+- (BOOL)needDraw {
+ return _needInitialize;
+}
+
+- (void)drawRegion:(GLRegion*)region rect:(CGRect)rect {
+ if (_textureSize.height() == 0 && _textureSize.width() == 0) {
+ return;
+ }
+
+ [Utility bindTextureForIOS:_textureId];
+
+ if (_needInitialize) {
+ glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ _textureSize.width(),
+ _textureSize.height(),
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ NULL);
+
+ [Utility logGLErrorCode:@"DesktopTexture initializeTextureSurfaceWithSize"];
+ _needInitialize = false;
+ }
+
+ [Utility drawSubRectToGLFromRectOfSize:_textureSize
+ subRect:webrtc::DesktopRect::MakeXYWH(
+ region->offset->x(),
+ region->offset->y(),
+ region->image->size().width(),
+ region->image->size().height())
+ data:region->image->data()];
+
+ [Utility logGLErrorCode:@"DesktopTexture drawRegion"];
+ // Release context
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+- (void)releaseTexture {
+ glDeleteTextures(1, &_textureId);
+}
+
+@end
diff --git a/remoting/ios/ui/help_view_controller.h b/remoting/ios/ui/help_view_controller.h
new file mode 100644
index 0000000000..036a7b5da7
--- /dev/null
+++ b/remoting/ios/ui/help_view_controller.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_HELP_VIEW_CONTROLLER_H_
+#define REMOTING_IOS_UI_HELP_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+@interface HelpViewController : UIViewController {
+ @private
+ IBOutlet UIWebView* _webView;
+}
+
+@end
+
+#endif // REMOTING_IOS_UI_HELP_VIEW_CONTROLLER_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/help_view_controller.mm b/remoting/ios/ui/help_view_controller.mm
new file mode 100644
index 0000000000..1a3c70562b
--- /dev/null
+++ b/remoting/ios/ui/help_view_controller.mm
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/help_view_controller.h"
+
+@implementation HelpViewController
+
+// Override UIViewController
+- (void)viewWillAppear:(BOOL)animated {
+ [self.navigationController setNavigationBarHidden:NO animated:YES];
+ NSString* string = @"https://support.google.com/chrome/answer/1649523";
+ NSURL* url = [NSURL URLWithString:string];
+ [_webView loadRequest:[NSURLRequest requestWithURL:url]];
+}
+
+@end
diff --git a/remoting/ios/ui/host_list_view_controller.h b/remoting/ios/ui/host_list_view_controller.h
new file mode 100644
index 0000000000..5f3c1bfd8b
--- /dev/null
+++ b/remoting/ios/ui/host_list_view_controller.h
@@ -0,0 +1,39 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_HOST_LIST_VIEW_CONTROLLER_H_
+#define REMOTING_IOS_UI_HOST_LIST_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+#import <GLKit/GLKit.h>
+
+#import "host_refresh.h"
+
+// HostListViewController presents the user with a list of hosts which has
+// been shared from other platforms to connect to
+@interface HostListViewController : UIViewController<HostRefreshDelegate,
+ UITableViewDelegate,
+ UITableViewDataSource> {
+ @private
+ IBOutlet UITableView* _tableHostList;
+ IBOutlet UIButton* _btnAccount;
+ IBOutlet UIActivityIndicatorView* _refreshActivityIndicator;
+ IBOutlet UIBarButtonItem* _versionInfo;
+
+ NSArray* _hostList;
+}
+
+@property(nonatomic, readonly) GTMOAuth2Authentication* authorization;
+@property(nonatomic, readonly) NSString* userEmail;
+
+// Triggered by UI 'refresh' button
+- (IBAction)btnRefreshHostListPressed:(id)sender;
+// Triggered by UI 'log in' button, if user is already logged in then the user
+// is logged out and a new session begins by requesting the user to log in,
+// possibly with a different account
+- (IBAction)btnAccountPressed:(id)sender;
+
+@end
+
+#endif // REMOTING_IOS_UI_HOST_LIST_VIEW_CONTROLLER_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/host_list_view_controller.mm b/remoting/ios/ui/host_list_view_controller.mm
new file mode 100644
index 0000000000..7dd7fa2b3c
--- /dev/null
+++ b/remoting/ios/ui/host_list_view_controller.mm
@@ -0,0 +1,229 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/host_list_view_controller.h"
+
+#import "remoting/ios/authorize.h"
+#import "remoting/ios/host.h"
+#import "remoting/ios/host_cell.h"
+#import "remoting/ios/host_refresh.h"
+#import "remoting/ios/utility.h"
+#import "remoting/ios/ui/host_view_controller.h"
+
+@interface HostListViewController (Private)
+- (void)refreshHostList;
+- (void)checkUserAndRefreshHostList;
+- (BOOL)isSignedIn;
+- (void)signInUser;
+// Callback from [Authorize createLoginController...]
+- (void)viewController:(UIViewController*)viewController
+ finishedWithAuth:(GTMOAuth2Authentication*)authResult
+ error:(NSError*)error;
+@end
+
+@implementation HostListViewController
+
+@synthesize userEmail = _userEmail;
+@synthesize authorization = _authorization;
+
+// Override default setter
+- (void)setAuthorization:(GTMOAuth2Authentication*)authorization {
+ _authorization = authorization;
+ if (_authorization.canAuthorize) {
+ _userEmail = _authorization.userEmail;
+ } else {
+ _userEmail = nil;
+ }
+
+ NSString* userName = _userEmail;
+
+ if (userName == nil) {
+ userName = @"Not logged in";
+ }
+
+ [_btnAccount setTitle:userName forState:UIControlStateNormal];
+
+ [self refreshHostList];
+}
+
+// Override UIViewController
+// Create google+ service for google authentication and oAuth2 authorization.
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ [_tableHostList setDataSource:self];
+ [_tableHostList setDelegate:self];
+
+ _versionInfo.title = [Utility appVersionNumberDisplayString];
+}
+
+// Override UIViewController
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:animated];
+ [self.navigationController setNavigationBarHidden:NO animated:NO];
+ [self setAuthorization:[Authorize getAnyExistingAuthorization]];
+}
+
+// Override UIViewController
+// Cancel segue when host status is not online
+- (BOOL)shouldPerformSegueWithIdentifier:(NSString*)identifier
+ sender:(id)sender {
+ if ([identifier isEqualToString:@"ConnectToHost"]) {
+ Host* host = [self hostAtIndex:[_tableHostList indexPathForCell:sender]];
+ if (![host.status isEqualToString:@"ONLINE"]) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
+// Override UIViewController
+// check for segues defined in the storyboard by identifier, and set a few
+// properties before transitioning
+- (void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender {
+ if ([segue.identifier isEqualToString:@"ConnectToHost"]) {
+ // the designationViewController type is defined by the storyboard
+ HostViewController* hostView =
+ static_cast<HostViewController*>(segue.destinationViewController);
+
+ NSString* authToken =
+ [_authorization.parameters valueForKey:@"access_token"];
+
+ if (authToken == nil) {
+ authToken = _authorization.authorizationTokenKey;
+ }
+
+ [hostView setHostDetails:[self hostAtIndex:[_tableHostList
+ indexPathForCell:sender]]
+ userEmail:_userEmail
+ authorizationToken:authToken];
+ }
+}
+
+// @protocol HostRefreshDelegate, remember received host list for the table
+// view to refresh from
+- (void)hostListRefresh:(NSArray*)hostList
+ errorMessage:(NSString*)errorMessage {
+ if (hostList != nil) {
+ _hostList = hostList;
+ [_tableHostList reloadData];
+ }
+ [_refreshActivityIndicator stopAnimating];
+ if (errorMessage != nil) {
+ [Utility showAlert:@"Host Refresh Failed" message:errorMessage];
+ }
+}
+
+// @protocol UITableViewDataSource
+// Only have 1 section and it contains all the hosts
+- (NSInteger)tableView:(UITableView*)tableView
+ numberOfRowsInSection:(NSInteger)section {
+ return [_hostList count];
+}
+
+// @protocol UITableViewDataSource
+// Convert a host entry to a table row
+- (HostCell*)tableView:(UITableView*)tableView
+ cellForRowAtIndexPath:(NSIndexPath*)indexPath {
+ static NSString* CellIdentifier = @"HostStatusCell";
+
+ HostCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier
+ forIndexPath:indexPath];
+
+ Host* host = [self hostAtIndex:indexPath];
+ cell.labelHostName.text = host.hostName;
+ cell.labelStatus.text = host.status;
+
+ UIColor* statColor = nil;
+ if ([host.status isEqualToString:@"ONLINE"]) {
+ statColor = [[UIColor alloc] initWithRed:0 green:1 blue:0 alpha:1];
+ } else {
+ statColor = [[UIColor alloc] initWithRed:1 green:0 blue:0 alpha:1];
+ }
+ [cell.labelStatus setTextColor:statColor];
+
+ return cell;
+}
+
+// @protocol UITableViewDataSource
+// Rows are not editable via standard UI mechanisms
+- (BOOL)tableView:(UITableView*)tableView
+ canEditRowAtIndexPath:(NSIndexPath*)indexPath {
+ return NO;
+}
+
+- (IBAction)btnRefreshHostListPressed:(id)sender {
+ [self refreshHostList];
+}
+
+- (IBAction)btnAccountPressed:(id)sender {
+ [self signInUser];
+}
+
+- (void)refreshHostList {
+ [_refreshActivityIndicator startAnimating];
+ _hostList = [[NSArray alloc] init];
+ [_tableHostList reloadData];
+
+ // Insert a small delay so the user is well informed that something is
+ // happening by the animating activity indicator
+ [self performSelector:@selector(checkUserAndRefreshHostList)
+ withObject:nil
+ afterDelay:.5];
+}
+
+// Most likely you want to call refreshHostList
+- (void)checkUserAndRefreshHostList {
+ if (![self isSignedIn]) {
+ [self signInUser];
+ } else {
+ HostRefresh* hostRefresh = [[HostRefresh alloc] init];
+ [hostRefresh refreshHostList:_authorization delegate:self];
+ }
+}
+
+- (BOOL)isSignedIn {
+ return (_userEmail != nil);
+}
+
+// Launch the google.com authentication and authorization process. If a user is
+// already signed in, begin by signing out so another account could be
+// signed in.
+- (void)signInUser {
+ [self presentViewController:
+ [Authorize createLoginController:self
+ finishedSelector:@selector(viewController:
+ finishedWithAuth:
+ error:)]
+ animated:YES
+ completion:nil];
+}
+
+// Callback from [Authorize createLoginController...]
+// Handle completion of the authentication process, and updates the service
+// with the new credentials.
+- (void)viewController:(UIViewController*)viewController
+ finishedWithAuth:(GTMOAuth2Authentication*)authResult
+ error:(NSError*)error {
+ [viewController.presentingViewController dismissViewControllerAnimated:NO
+ completion:nil];
+
+ if (error != nil) {
+ [Utility showAlert:@"Authentication Error"
+ message:error.localizedDescription];
+ [self setAuthorization:nil];
+ } else {
+ [self setAuthorization:authResult];
+ }
+}
+
+- (Host*)hostAtIndex:(NSIndexPath*)indexPath {
+ return [_hostList objectAtIndex:indexPath.row];
+}
+
+@end
diff --git a/remoting/ios/ui/host_list_view_controller_unittest.mm b/remoting/ios/ui/host_list_view_controller_unittest.mm
new file mode 100644
index 0000000000..7e75739c05
--- /dev/null
+++ b/remoting/ios/ui/host_list_view_controller_unittest.mm
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/host_list_view_controller.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+#import "remoting/ios/host.h"
+#import "remoting/ios/host_refresh_test_helper.h"
+#import "remoting/ios/ui/host_view_controller.h"
+
+namespace remoting {
+
+class HostListViewControllerTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ controller_ = [[HostListViewController alloc] init];
+ SetHostByCount(1);
+ }
+
+ void SetHostByCount(int numHosts) {
+ NSArray* array =
+ [Host parseListFromJSON:HostRefreshTestHelper::GetHostList(numHosts)];
+ RefreshHostList(array);
+ }
+
+ void SetHostByString(NSString* string) {
+ NSArray* array =
+ [Host parseListFromJSON:HostRefreshTestHelper::GetHostList(string)];
+ RefreshHostList(array);
+ }
+
+ void RefreshHostList(NSArray* array) {
+ [controller_ hostListRefresh:array errorMessage:nil];
+ }
+
+ HostListViewController* controller_;
+};
+
+TEST_F(HostListViewControllerTest, DefaultAuthorization) {
+ ASSERT_TRUE(controller_.authorization == nil);
+
+ [controller_ viewWillAppear:YES];
+
+ ASSERT_TRUE(controller_.authorization != nil);
+}
+
+TEST_F(HostListViewControllerTest, hostListRefresh) {
+ SetHostByCount(2);
+ ASSERT_EQ(2, [controller_ tableView:nil numberOfRowsInSection:0]);
+
+ SetHostByCount(10);
+ ASSERT_EQ(10, [controller_ tableView:nil numberOfRowsInSection:0]);
+}
+
+TEST_F(HostListViewControllerTest,
+ ShouldPerformSegueWithIdentifierOfConnectToHost) {
+ ASSERT_FALSE([controller_ shouldPerformSegueWithIdentifier:@"ConnectToHost"
+ sender:nil]);
+
+ NSString* host = HostRefreshTestHelper::GetMultipleHosts(1);
+ host = [host stringByReplacingOccurrencesOfString:@"TESTING"
+ withString:@"ONLINE"];
+ SetHostByString(host);
+ ASSERT_TRUE([controller_ shouldPerformSegueWithIdentifier:@"ConnectToHost"
+ sender:nil]);
+}
+
+TEST_F(HostListViewControllerTest, prepareSegueWithIdentifierOfConnectToHost) {
+ HostViewController* destination = [[HostViewController alloc] init];
+
+ ASSERT_NSNE(HostRefreshTestHelper::HostNameTest, destination.host.hostName);
+
+ UIStoryboardSegue* seque =
+ [[UIStoryboardSegue alloc] initWithIdentifier:@"ConnectToHost"
+ source:controller_
+ destination:destination];
+
+ [controller_ prepareForSegue:seque sender:nil];
+
+ ASSERT_NSEQ(HostRefreshTestHelper::HostNameTest, destination.host.hostName);
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/ui/host_view_controller.h b/remoting/ios/ui/host_view_controller.h
new file mode 100644
index 0000000000..6e0f287948
--- /dev/null
+++ b/remoting/ios/ui/host_view_controller.h
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_HOST_VIEW_CONTROLLER_H_
+#define REMOTING_IOS_UI_HOST_VIEW_CONTROLLER_H_
+
+#import <GLKit/GLKit.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+#import "remoting/ios/host.h"
+#import "remoting/ios/key_input.h"
+#import "remoting/ios/utility.h"
+#import "remoting/ios/bridge/host_proxy.h"
+#import "remoting/ios/ui/desktop_texture.h"
+#import "remoting/ios/ui/cursor_texture.h"
+#import "remoting/ios/ui/pin_entry_view_controller.h"
+#import "remoting/ios/ui/scene_view.h"
+
+@interface HostViewController
+ : GLKViewController<PinEntryViewControllerDelegate,
+ KeyInputDelegate,
+ // Communication channel from HOST to CLIENT
+ ClientProxyDelegate,
+ UIGestureRecognizerDelegate,
+ UIToolbarDelegate> {
+ @private
+ IBOutlet UIActivityIndicatorView* _busyIndicator;
+ IBOutlet UIButton* _barBtnDisconnect;
+ IBOutlet UIButton* _barBtnKeyboard;
+ IBOutlet UIButton* _barBtnNavigation;
+ IBOutlet UIButton* _barBtnCtrlAltDel;
+ IBOutlet UILongPressGestureRecognizer* _longPressRecognizer;
+ IBOutlet UIPanGestureRecognizer* _panRecognizer;
+ IBOutlet UIPanGestureRecognizer* _threeFingerPanRecognizer;
+ IBOutlet UIPinchGestureRecognizer* _pinchRecognizer;
+ IBOutlet UITapGestureRecognizer* _singleTapRecognizer;
+ IBOutlet UITapGestureRecognizer* _twoFingerTapRecognizer;
+ IBOutlet UITapGestureRecognizer* _threeFingerTapRecognizer;
+ IBOutlet UIToolbar* _toolbar;
+ IBOutlet UIToolbar* _hiddenToolbar;
+ IBOutlet NSLayoutConstraint* _toolBarYPosition;
+ IBOutlet NSLayoutConstraint* _hiddenToolbarYPosition;
+
+ KeyInput* _keyEntryView;
+ NSString* _statusMessage;
+
+ // The GLES2 context being drawn too.
+ EAGLContext* _context;
+
+ // GLKBaseEffect encapsulates the GL Shaders needed to draw at most two
+ // textures |_textureIds| given vertex information. The draw surface consists
+ // of two layers (GL Textures). The bottom layer is the desktop of the HOST.
+ // The top layer is mostly transparent and is used to overlay the current
+ // cursor.
+ GLKBaseEffect* _effect;
+
+ // All the details needed to draw our GL Scene, and our two textures.
+ SceneView* _scene;
+ DesktopTexture* _desktop;
+ CursorTexture* _mouse;
+
+ // List of regions and data that have pending draws to |_desktop| .
+ ScopedVector<GLRegion> _glRegions;
+
+ // Lock for |_glRegions|, regions are delivered from HOST on another thread,
+ // and drawn to |_desktop| from a GL Context thread
+ NSLock* _glBufferLock;
+
+ // Lock for |_mouse.cursor|, cursor updates are delivered from HOST on another
+ // thread, and drawn to |_mouse| from a GL Context thread
+ NSLock* _glCursorLock;
+
+ // Communication channel from CLIENT to HOST
+ HostProxy* _clientToHostProxy;
+}
+
+// Details for the host and user
+@property(nonatomic, readonly) Host* host;
+@property(nonatomic, readonly) NSString* userEmail;
+@property(nonatomic, readonly) NSString* userAuthorizationToken;
+
+- (void)setHostDetails:(Host*)host
+ userEmail:(NSString*)userEmail
+ authorizationToken:(NSString*)authorizationToken;
+
+// Zoom in/out
+- (IBAction)pinchGestureTriggered:(UIPinchGestureRecognizer*)sender;
+// Left mouse click, moves cursor
+- (IBAction)tapGestureTriggered:(UITapGestureRecognizer*)sender;
+// Scroll the view in 2d
+- (IBAction)panGestureTriggered:(UIPanGestureRecognizer*)sender;
+// Right mouse click and drag, moves cursor
+- (IBAction)longPressGestureTriggered:(UILongPressGestureRecognizer*)sender;
+// Right mouse click
+- (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender;
+// Middle mouse click
+- (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender;
+// Show hidden menus. Swipe up for keyboard, swipe down for navigation menu
+- (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender;
+
+// Do navigation 'back'
+- (IBAction)barBtnNavigationBackPressed:(id)sender;
+// Show keyboard
+- (IBAction)barBtnKeyboardPressed:(id)sender;
+// Trigger |_toolbar| animation
+- (IBAction)barBtnToolBarHidePressed:(id)sender;
+// Send Keys for ctrl, atl, delete
+- (IBAction)barBtnCtrlAltDelPressed:(id)sender;
+
+@end
+
+#endif // REMOTING_IOS_UI_HOST_VIEW_CONTROLLER_H_
diff --git a/remoting/ios/ui/host_view_controller.mm b/remoting/ios/ui/host_view_controller.mm
new file mode 100644
index 0000000000..d87e7674bd
--- /dev/null
+++ b/remoting/ios/ui/host_view_controller.mm
@@ -0,0 +1,676 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/host_view_controller.h"
+
+#include <OpenGLES/ES2/gl.h>
+
+#import "remoting/ios/data_store.h"
+
+namespace {
+
+// TODO (aboone) Some of the layout is not yet set in stone, so variables have
+// been used to position and turn items on and off. Eventually these may be
+// stabilized and removed.
+
+// Scroll speed multiplier for mouse wheel
+const static int kMouseWheelSensitivity = 20;
+
+// Area the navigation bar consumes when visible in pixels
+const static int kTopMargin = 20;
+// Area the footer consumes when visible (no footer currently exists)
+const static int kBottomMargin = 0;
+
+} // namespace
+
+@interface HostViewController (Private)
+- (void)setupGL;
+- (void)tearDownGL;
+- (void)goBack;
+- (void)updateLabels;
+- (BOOL)isToolbarHidden;
+- (void)updatePanVelocityShouldCancel:(bool)canceled;
+- (void)orientationChanged:(NSNotification*)note;
+- (void)applySceneChange:(CGPoint)translation scaleBy:(float)ratio;
+- (void)showToolbar:(BOOL)visible;
+@end
+
+@implementation HostViewController
+
+@synthesize host = _host;
+@synthesize userEmail = _userEmail;
+@synthesize userAuthorizationToken = _userAuthorizationToken;
+
+// Override UIViewController
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ DCHECK(_context);
+ static_cast<GLKView*>(self.view).context = _context;
+
+ [_keyEntryView setDelegate:self];
+
+ _clientToHostProxy = [[HostProxy alloc] init];
+
+ // There is a 1 pixel top border which is actually the background not being
+ // covered. There is no obvious way to remove that pixel 'border'. Set the
+ // background clear, and also reset the backgroundimage and shawdowimage to an
+ // empty image any time the view is moved.
+ _hiddenToolbar.backgroundColor = [UIColor clearColor];
+ if ([_hiddenToolbar respondsToSelector:@selector(setBackgroundImage:
+ forToolbarPosition:
+ barMetrics:)]) {
+ [_hiddenToolbar setBackgroundImage:[UIImage new]
+ forToolbarPosition:UIToolbarPositionAny
+ barMetrics:UIBarMetricsDefault];
+ }
+ if ([_hiddenToolbar
+ respondsToSelector:@selector(setShadowImage:forToolbarPosition:)]) {
+ [_hiddenToolbar setShadowImage:[UIImage new]
+ forToolbarPosition:UIToolbarPositionAny];
+ }
+
+ // 1/2 circle rotation for an icon ~ 180 degree ~ 1 radian
+ _barBtnNavigation.imageView.transform = CGAffineTransformMakeRotation(M_PI);
+
+ _scene = [[SceneView alloc] init];
+ [_scene setMarginsFromLeft:0 right:0 top:kTopMargin bottom:kBottomMargin];
+ _desktop = [[DesktopTexture alloc] init];
+ _mouse = [[CursorTexture alloc] init];
+
+ _glBufferLock = [[NSLock alloc] init];
+ _glCursorLock = [[NSLock alloc] init];
+
+ [_scene
+ setContentSize:[Utility getOrientatedSize:self.view.bounds.size
+ shouldWidthBeLongestSide:[Utility isInLandscapeMode]]];
+ [self showToolbar:YES];
+ [self updateLabels];
+
+ [self setupGL];
+
+ [_singleTapRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer];
+ [_twoFingerTapRecognizer
+ requireGestureRecognizerToFail:_threeFingerTapRecognizer];
+ //[_pinchRecognizer requireGestureRecognizerToFail:_twoFingerTapRecognizer];
+ [_panRecognizer requireGestureRecognizerToFail:_singleTapRecognizer];
+ [_threeFingerPanRecognizer
+ requireGestureRecognizerToFail:_threeFingerTapRecognizer];
+ //[_pinchRecognizer requireGestureRecognizerToFail:_threeFingerPanRecognizer];
+
+ // Subscribe to changes in orientation
+ [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(orientationChanged:)
+ name:UIDeviceOrientationDidChangeNotification
+ object:[UIDevice currentDevice]];
+}
+
+- (void)setupGL {
+ [EAGLContext setCurrentContext:_context];
+
+ _effect = [[GLKBaseEffect alloc] init];
+ [Utility logGLErrorCode:@"setupGL begin"];
+
+ // Initialize each texture
+ [_desktop bindToEffect:[_effect texture2d0]];
+ [_mouse bindToEffect:[_effect texture2d1]];
+ [Utility logGLErrorCode:@"setupGL textureComplete"];
+}
+
+// Override UIViewController
+- (void)viewDidUnload {
+ [super viewDidUnload];
+ [self tearDownGL];
+
+ if ([EAGLContext currentContext] == _context) {
+ [EAGLContext setCurrentContext:nil];
+ }
+ _context = nil;
+}
+
+- (void)tearDownGL {
+ [EAGLContext setCurrentContext:_context];
+
+ // Release Textures
+ [_desktop releaseTexture];
+ [_mouse releaseTexture];
+}
+
+// Override UIViewController
+- (void)viewWillAppear:(BOOL)animated {
+ [super viewWillAppear:NO];
+ [self.navigationController setNavigationBarHidden:YES animated:YES];
+ [self updateLabels];
+ if (![_clientToHostProxy isConnected]) {
+ [_busyIndicator startAnimating];
+
+ [_clientToHostProxy connectToHost:_userEmail
+ authToken:_userAuthorizationToken
+ jabberId:_host.jabberId
+ hostId:_host.hostId
+ publicKey:_host.publicKey
+ delegate:self];
+ }
+}
+
+// Override UIViewController
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:NO];
+ NSArray* viewControllers = self.navigationController.viewControllers;
+ if (viewControllers.count > 1 &&
+ [viewControllers objectAtIndex:viewControllers.count - 2] == self) {
+ // View is disappearing because a new view controller was pushed onto the
+ // stack
+ } else if ([viewControllers indexOfObject:self] == NSNotFound) {
+ // View is disappearing because it was popped from the stack
+ [_clientToHostProxy disconnectFromHost];
+ }
+}
+
+// "Back" goes to the root controller for now
+- (void)goBack {
+ [self.navigationController popToRootViewControllerAnimated:YES];
+}
+
+// @protocol PinEntryViewControllerDelegate
+// Return the PIN input by User, indicate if the User should be prompted to
+// re-enter the pin in the future
+- (void)connectToHostWithPin:(UIViewController*)controller
+ hostPin:(NSString*)hostPin
+ shouldPrompt:(BOOL)shouldPrompt {
+ const HostPreferences* hostPrefs =
+ [[DataStore sharedStore] getHostForId:_host.hostId];
+ if (!hostPrefs) {
+ hostPrefs = [[DataStore sharedStore] createHost:_host.hostId];
+ }
+ if (hostPrefs) {
+ hostPrefs.hostPin = hostPin;
+ hostPrefs.askForPin = [NSNumber numberWithBool:shouldPrompt];
+ [[DataStore sharedStore] saveChanges];
+ }
+
+ [[controller presentingViewController] dismissViewControllerAnimated:NO
+ completion:nil];
+
+ [_clientToHostProxy authenticationResponse:hostPin createPair:!shouldPrompt];
+}
+
+// @protocol PinEntryViewControllerDelegate
+// Returns if the user canceled while entering their PIN
+- (void)cancelledConnectToHostWithPin:(UIViewController*)controller {
+ [[controller presentingViewController] dismissViewControllerAnimated:NO
+ completion:nil];
+
+ [self goBack];
+}
+
+- (void)setHostDetails:(Host*)host
+ userEmail:(NSString*)userEmail
+ authorizationToken:(NSString*)authorizationToken {
+ DCHECK(host.jabberId);
+ _host = host;
+ _userEmail = userEmail;
+ _userAuthorizationToken = authorizationToken;
+}
+
+// Set various labels on the form for iPad vs iPhone, and orientation
+- (void)updateLabels {
+ if (![Utility isPad] && ![Utility isInLandscapeMode]) {
+ [_barBtnDisconnect setTitle:@"" forState:(UIControlStateNormal)];
+ [_barBtnCtrlAltDel setTitle:@"CtAtD" forState:UIControlStateNormal];
+ } else {
+ [_barBtnCtrlAltDel setTitle:@"Ctrl+Alt+Del" forState:UIControlStateNormal];
+
+ NSString* hostStatus = _host.hostName;
+ if (![_statusMessage isEqual:@"Connected"]) {
+ hostStatus = [NSString
+ stringWithFormat:@"%@ - %@", _host.hostName, _statusMessage];
+ }
+ [_barBtnDisconnect setTitle:hostStatus forState:UIControlStateNormal];
+ }
+
+ [_barBtnDisconnect sizeToFit];
+ [_barBtnCtrlAltDel sizeToFit];
+}
+
+// Resize the view of the desktop - Zoom in/out. This can occur during a Pan.
+- (IBAction)pinchGestureTriggered:(UIPinchGestureRecognizer*)sender {
+ if ([sender state] == UIGestureRecognizerStateChanged) {
+ [self applySceneChange:CGPointMake(0.0, 0.0) scaleBy:sender.scale];
+
+ sender.scale = 1.0; // reset scale so next iteration is a relative ratio
+ }
+}
+
+- (IBAction)tapGestureTriggered:(UITapGestureRecognizer*)sender {
+ if ([_scene containsTouchPoint:[sender locationInView:self.view]]) {
+ [Utility leftClickOn:_clientToHostProxy at:_scene.mousePosition];
+ }
+}
+
+// Change position of scene. This can occur during a pinch or longpress.
+// Or perform a Mouse Wheel Scroll
+- (IBAction)panGestureTriggered:(UIPanGestureRecognizer*)sender {
+ CGPoint translation = [sender translationInView:self.view];
+
+ // If we start with 2 touches, and the pinch gesture is not in progress yet,
+ // then disable it, so mouse scrolling and zoom do not occur at the same
+ // time.
+ if ([sender numberOfTouches] == 2 &&
+ [sender state] == UIGestureRecognizerStateBegan &&
+ !(_pinchRecognizer.state == UIGestureRecognizerStateBegan ||
+ _pinchRecognizer.state == UIGestureRecognizerStateChanged)) {
+ _pinchRecognizer.enabled = NO;
+ }
+
+ if (!_pinchRecognizer.enabled) {
+ // Began with 2 touches, so this is a scroll event
+ translation.x *= kMouseWheelSensitivity;
+ translation.y *= kMouseWheelSensitivity;
+ [Utility mouseScroll:_clientToHostProxy
+ at:_scene.mousePosition
+ delta:webrtc::DesktopVector(translation.x, translation.y)];
+ } else {
+ // Did not begin with 2 touches, doing a pan event
+ if ([sender state] == UIGestureRecognizerStateChanged) {
+ CGPoint translation = [sender translationInView:self.view];
+
+ [self applySceneChange:translation scaleBy:1.0];
+
+ } else if ([sender state] == UIGestureRecognizerStateEnded) {
+ // After user removes their fingers from the screen, apply an acceleration
+ // effect
+ [_scene setPanVelocity:[sender velocityInView:self.view]];
+ }
+ }
+
+ // Finished the event chain
+ if (!([sender state] == UIGestureRecognizerStateBegan ||
+ [sender state] == UIGestureRecognizerStateChanged)) {
+ _pinchRecognizer.enabled = YES;
+ }
+
+ // Reset translation so next iteration is relative.
+ [sender setTranslation:CGPointZero inView:self.view];
+}
+
+// Click-Drag mouse operation. This can occur during a Pan.
+- (IBAction)longPressGestureTriggered:(UILongPressGestureRecognizer*)sender {
+
+ if ([sender state] == UIGestureRecognizerStateBegan) {
+ [_clientToHostProxy mouseAction:_scene.mousePosition
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:1
+ buttonDown:YES];
+ } else if (!([sender state] == UIGestureRecognizerStateBegan ||
+ [sender state] == UIGestureRecognizerStateChanged)) {
+ [_clientToHostProxy mouseAction:_scene.mousePosition
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:1
+ buttonDown:NO];
+ }
+}
+
+- (IBAction)twoFingerTapGestureTriggered:(UITapGestureRecognizer*)sender {
+ if ([_scene containsTouchPoint:[sender locationInView:self.view]]) {
+ [Utility rightClickOn:_clientToHostProxy at:_scene.mousePosition];
+ }
+}
+
+- (IBAction)threeFingerTapGestureTriggered:(UITapGestureRecognizer*)sender {
+
+ if ([_scene containsTouchPoint:[sender locationInView:self.view]]) {
+ [Utility middleClickOn:_clientToHostProxy at:_scene.mousePosition];
+ }
+}
+
+- (IBAction)threeFingerPanGestureTriggered:(UIPanGestureRecognizer*)sender {
+ if ([sender state] == UIGestureRecognizerStateChanged) {
+ CGPoint translation = [sender translationInView:self.view];
+ if (translation.y > 0) {
+ // Swiped down
+ [self showToolbar:YES];
+ } else if (translation.y < 0) {
+ // Swiped up
+ [_keyEntryView becomeFirstResponder];
+ [self updateLabels];
+ }
+ [sender setTranslation:CGPointZero inView:self.view];
+ }
+}
+
+- (IBAction)barBtnNavigationBackPressed:(id)sender {
+ [self goBack];
+}
+
+- (IBAction)barBtnKeyboardPressed:(id)sender {
+ if ([_keyEntryView isFirstResponder]) {
+ [_keyEntryView endEditing:NO];
+ } else {
+ [_keyEntryView becomeFirstResponder];
+ }
+
+ [self updateLabels];
+}
+
+- (IBAction)barBtnToolBarHidePressed:(id)sender {
+ [self showToolbar:[self isToolbarHidden]]; // Toolbar is either on
+ // screen or off screen
+}
+
+- (IBAction)barBtnCtrlAltDelPressed:(id)sender {
+ [_keyEntryView ctrlAltDel];
+}
+
+// Override UIResponder
+// When any gesture begins, remove any acceleration effects currently being
+// applied. Example, Panning view and let it shoot off into the distance, but
+// then I see a spot I'm interested in so I will touch to capture that locations
+// focus.
+- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
+ [self updatePanVelocityShouldCancel:YES];
+ [super touchesBegan:touches withEvent:event];
+}
+
+// @protocol UIGestureRecognizerDelegate
+// Allow panning and zooming to occur simultaneously.
+// Allow panning and long press to occur simultaneously.
+// Pinch requires 2 touches, and long press requires a single touch, so they are
+// mutually exclusive regardless of if panning is the initiating gesture
+- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
+ shouldRecognizeSimultaneouslyWithGestureRecognizer:
+ (UIGestureRecognizer*)otherGestureRecognizer {
+ if (gestureRecognizer == _pinchRecognizer ||
+ (gestureRecognizer == _panRecognizer)) {
+ if (otherGestureRecognizer == _pinchRecognizer ||
+ otherGestureRecognizer == _panRecognizer) {
+ return YES;
+ }
+ }
+
+ if (gestureRecognizer == _longPressRecognizer ||
+ gestureRecognizer == _panRecognizer) {
+ if (otherGestureRecognizer == _longPressRecognizer ||
+ otherGestureRecognizer == _panRecognizer) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+// @protocol ClientControllerDelegate
+// Prompt the user for their PIN if pairing has not already been established
+- (void)requestHostPin:(BOOL)pairingSupported {
+ BOOL requestPin = YES;
+ const HostPreferences* hostPrefs =
+ [[DataStore sharedStore] getHostForId:_host.hostId];
+ if (hostPrefs) {
+ requestPin = [hostPrefs.askForPin boolValue];
+ if (!requestPin) {
+ if (hostPrefs.hostPin == nil || hostPrefs.hostPin.length == 0) {
+ requestPin = YES;
+ }
+ }
+ }
+ if (requestPin == YES) {
+ PinEntryViewController* pinEntry = [[PinEntryViewController alloc] init];
+ [pinEntry setDelegate:self];
+ [pinEntry setHostName:_host.hostName];
+ [pinEntry setShouldPrompt:YES];
+ [pinEntry setPairingSupported:pairingSupported];
+
+ [self presentViewController:pinEntry animated:YES completion:nil];
+ } else {
+ [_clientToHostProxy authenticationResponse:hostPrefs.hostPin
+ createPair:pairingSupported];
+ }
+}
+
+// @protocol ClientControllerDelegate
+// Occurs when a connection to a HOST is established successfully
+- (void)connected {
+ // Everything is good, nothing to do
+}
+
+// @protocol ClientControllerDelegate
+- (void)connectionStatus:(NSString*)statusMessage {
+ _statusMessage = statusMessage;
+
+ if ([_statusMessage isEqual:@"Connection closed"]) {
+ [self goBack];
+ } else {
+ [self updateLabels];
+ }
+}
+
+// @protocol ClientControllerDelegate
+// Occurs when a connection to a HOST has failed
+- (void)connectionFailed:(NSString*)errorMessage {
+ [_busyIndicator stopAnimating];
+ NSString* errorMsg;
+ if ([_clientToHostProxy isConnected]) {
+ errorMsg = @"Lost Connection";
+ } else {
+ errorMsg = @"Unable to connect";
+ }
+ [Utility showAlert:errorMsg message:errorMessage];
+ [self goBack];
+}
+
+// @protocol ClientControllerDelegate
+// Copy the updated regions to a backing store to be consumed by the GL Context
+// on a different thread. A region is stored in disjoint memory locations, and
+// must be transformed to a contiguous memory buffer for a GL Texture write.
+// /-----\
+// | 2-4| This buffer is 5x3 bytes large, a region exists at bytes 2 to 4 and
+// | 7-9| bytes 7 to 9. The region is extracted to a new contiguous buffer
+// | | of 6 bytes in length.
+// \-----/
+// More than 1 region may exist in the frame from each call, in which case a new
+// buffer is created for each region
+- (void)applyFrame:(const webrtc::DesktopSize&)size
+ stride:(NSInteger)stride
+ data:(uint8_t*)data
+ regions:(const std::vector<webrtc::DesktopRect>&)regions {
+ [_glBufferLock lock]; // going to make changes to |_glRegions|
+
+ if (!_scene.frameSize.equals(size)) {
+ // When this is the initial frame, the busyIndicator is still spinning. Now
+ // is a good time to stop it.
+ [_busyIndicator stopAnimating];
+
+ // If the |_toolbar| is still showing, hide it.
+ [self showToolbar:NO];
+ [_scene setContentSize:
+ [Utility getOrientatedSize:self.view.bounds.size
+ shouldWidthBeLongestSide:[Utility isInLandscapeMode]]];
+ [_scene setFrameSize:size];
+ [_desktop setTextureSize:size];
+ [_mouse setTextureSize:size];
+ }
+
+ uint32_t src_stride = stride;
+
+ for (uint32_t i = 0; i < regions.size(); i++) {
+ scoped_ptr<GLRegion> region(new GLRegion());
+
+ if (region.get()) {
+ webrtc::DesktopRect rect = regions.at(i);
+
+ webrtc::DesktopSize(rect.width(), rect.height());
+ region->offset.reset(new webrtc::DesktopVector(rect.left(), rect.top()));
+ region->image.reset(new webrtc::BasicDesktopFrame(
+ webrtc::DesktopSize(rect.width(), rect.height())));
+
+ if (region->image->data()) {
+ uint32_t bytes_per_row =
+ region->image->kBytesPerPixel * region->image->size().width();
+
+ uint32_t offset =
+ (src_stride * region->offset->y()) + // row
+ (region->offset->x() * region->image->kBytesPerPixel); // column
+
+ uint8_t* src_buffer = data + offset;
+ uint8_t* dst_buffer = region->image->data();
+
+ // row by row copy
+ for (uint32_t j = 0; j < region->image->size().height(); j++) {
+ memcpy(dst_buffer, src_buffer, bytes_per_row);
+ dst_buffer += bytes_per_row;
+ src_buffer += src_stride;
+ }
+ _glRegions.push_back(region.release());
+ }
+ }
+ }
+ [_glBufferLock unlock]; // done making changes to |_glRegions|
+}
+
+// @protocol ClientControllerDelegate
+// Copy the delivered cursor to a backing store to be consumed by the GL Context
+// on a different thread. Note only the most recent cursor is of importance,
+// discard the previous cursor.
+- (void)applyCursor:(const webrtc::DesktopSize&)size
+ hotspot:(const webrtc::DesktopVector&)hotspot
+ cursorData:(uint8_t*)data {
+
+ [_glCursorLock lock]; // going to make changes to |_cursor|
+
+ // MouseCursor takes ownership of DesktopFrame
+ [_mouse setCursor:new webrtc::MouseCursor(new webrtc::BasicDesktopFrame(size),
+ hotspot)];
+
+ if (_mouse.cursor.image().data()) {
+ memcpy(_mouse.cursor.image().data(),
+ data,
+ size.width() * size.height() * _mouse.cursor.image().kBytesPerPixel);
+ } else {
+ [_mouse setCursor:NULL];
+ }
+
+ [_glCursorLock unlock]; // done making changes to |_cursor|
+}
+
+// @protocol GLKViewDelegate
+// There is quite a few gotchas involved in working with this function. For
+// sanity purposes, I've just assumed calls to the function are on a different
+// thread which I've termed GL Context. Any variables consumed by this function
+// should be thread safe.
+//
+// Clear Screen, update desktop, update cursor, define position, and finally
+// present
+//
+// In general, avoid expensive work in this function to maximize frame rate.
+- (void)glkView:(GLKView*)view drawInRect:(CGRect)rect {
+ [self updatePanVelocityShouldCancel:NO];
+
+ // Clear to black, to give the background color
+ glClearColor(0.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ [Utility logGLErrorCode:@"drawInRect bindBuffer"];
+
+ if (_glRegions.size() > 0 || [_desktop needDraw]) {
+ [_glBufferLock lock];
+
+ for (uint32_t i = 0; i < _glRegions.size(); i++) {
+ // |_glRegions[i].data| has been properly ordered by [self applyFrame]
+ [_desktop drawRegion:_glRegions[i] rect:rect];
+ }
+
+ _glRegions.clear();
+ [_glBufferLock unlock];
+ }
+
+ if ([_mouse needDrawAtPosition:_scene.mousePosition]) {
+ [_glCursorLock lock];
+ [_mouse drawWithMousePosition:_scene.mousePosition];
+ [_glCursorLock unlock];
+ }
+
+ [_effect transform].projectionMatrix = _scene.projectionMatrix;
+ [_effect transform].modelviewMatrix = _scene.modelViewMatrix;
+ [_effect prepareToDraw];
+
+ [Utility logGLErrorCode:@"drawInRect prepareToDrawComplete"];
+
+ [_scene draw];
+}
+
+// @protocol KeyInputDelegate
+- (void)keyboardDismissed {
+ [self updateLabels];
+}
+
+// @protocol KeyInputDelegate
+// Send keyboard input to HOST
+- (void)keyboardActionKeyCode:(uint32_t)keyPressed isKeyDown:(BOOL)keyDown {
+ [_clientToHostProxy keyboardAction:keyPressed keyDown:keyDown];
+}
+
+- (BOOL)isToolbarHidden {
+ return (_toolbar.frame.origin.y < 0);
+}
+
+// Update the scene acceleration vector
+- (void)updatePanVelocityShouldCancel:(bool)canceled {
+ if (canceled) {
+ [_scene setPanVelocity:CGPointMake(0, 0)];
+ }
+ BOOL inMotion = [_scene tickPanVelocity];
+
+ _singleTapRecognizer.enabled = !inMotion;
+ _longPressRecognizer.enabled = !inMotion;
+}
+
+- (void)applySceneChange:(CGPoint)translation scaleBy:(float)ratio {
+ [_scene panAndZoom:translation scaleBy:ratio];
+ // Notify HOST that the mouse moved
+ [Utility moveMouse:_clientToHostProxy at:_scene.mousePosition];
+}
+
+// Callback from NSNotificationCenter when the User changes orientation
+- (void)orientationChanged:(NSNotification*)note {
+ [_scene
+ setContentSize:[Utility getOrientatedSize:self.view.bounds.size
+ shouldWidthBeLongestSide:[Utility isInLandscapeMode]]];
+ [self showToolbar:![self isToolbarHidden]];
+ [self updateLabels];
+}
+
+// Animate |_toolbar| by moving it on or offscreen
+- (void)showToolbar:(BOOL)visible {
+ CGRect frame = [_toolbar frame];
+
+ _toolBarYPosition.constant = -frame.size.height;
+ int topOffset = kTopMargin;
+
+ if (visible) {
+ topOffset += frame.size.height;
+ _toolBarYPosition.constant = kTopMargin;
+ }
+
+ _hiddenToolbarYPosition.constant = topOffset;
+ [_scene setMarginsFromLeft:0 right:0 top:topOffset bottom:kBottomMargin];
+
+ // hidden when |_toolbar| is |visible|
+ _hiddenToolbar.hidden = (visible == YES);
+
+ [UIView animateWithDuration:0.5
+ animations:^{ [self.view layoutIfNeeded]; }
+ completion:^(BOOL finished) {// Nothing to do for now
+ }];
+
+ // Center view if needed for any reason.
+ // Specificallly, if the top anchor is active.
+ [self applySceneChange:CGPointMake(0.0, 0.0) scaleBy:1.0];
+}
+@end
diff --git a/remoting/ios/ui/pin_entry_view_controller.h b/remoting/ios/ui/pin_entry_view_controller.h
new file mode 100644
index 0000000000..aaf854a145
--- /dev/null
+++ b/remoting/ios/ui/pin_entry_view_controller.h
@@ -0,0 +1,49 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_PIN_ENTRY_VIEW_CONTROLLER_H_
+#define REMOTING_IOS_UI_PIN_ENTRY_VIEW_CONTROLLER_H_
+
+#import <UIKit/UIKit.h>
+
+// Contract to handle finalization for Pin Prompt
+@protocol PinEntryViewControllerDelegate<NSObject>
+
+// Returns with user's Pin. Pin has not been validated with the server yet.
+// |shouldPrompt| indicates whether a prompt should be needed for the next login
+// attempt with this host.
+- (void)connectToHostWithPin:(UIViewController*)controller
+ hostPin:(NSString*)hostPin
+ shouldPrompt:(BOOL)shouldPrompt;
+
+// Returns when the user has cancelled the input, effectively closing the
+// connection attempt.
+- (void)cancelledConnectToHostWithPin:(UIViewController*)controller;
+
+@end
+
+// Dialog for user's Pin input. If a host has |pairingSupported| then user has
+// the option to save a token for authentication.
+@interface PinEntryViewController : UIViewController<UITextFieldDelegate> {
+ @private
+ IBOutlet UIView* _controlView;
+ IBOutlet UIButton* _cancelButton;
+ IBOutlet UIButton* _connectButton;
+ IBOutlet UILabel* _host;
+ IBOutlet UISwitch* _switchAskAgain;
+ IBOutlet UILabel* _shouldSavePin;
+ IBOutlet UITextField* _hostPin;
+}
+
+@property(weak, nonatomic) id<PinEntryViewControllerDelegate> delegate;
+@property(nonatomic, copy) NSString* hostName;
+@property(nonatomic) BOOL shouldPrompt;
+@property(nonatomic) BOOL pairingSupported;
+
+- (IBAction)buttonCancelClicked:(id)sender;
+- (IBAction)buttonConnectClicked:(id)sender;
+
+@end
+
+#endif // REMOTING_IOS_UI_PIN_ENTRY_VIEW_CONTROLLER_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/pin_entry_view_controller.mm b/remoting/ios/ui/pin_entry_view_controller.mm
new file mode 100644
index 0000000000..9a37677a50
--- /dev/null
+++ b/remoting/ios/ui/pin_entry_view_controller.mm
@@ -0,0 +1,71 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/pin_entry_view_controller.h"
+
+#import "remoting/ios/utility.h"
+
+@implementation PinEntryViewController
+
+@synthesize delegate = _delegate;
+@synthesize shouldPrompt = _shouldPrompt;
+@synthesize pairingSupported = _pairingSupported;
+
+// Override UIViewController
+- (id)initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil {
+ // NibName is the * part of your *.xib file
+
+ if ([Utility isPad]) {
+ self = [super initWithNibName:@"pin_entry_view_controller_ipad" bundle:nil];
+ } else {
+ self =
+ [super initWithNibName:@"pin_entry_view_controller_iphone" bundle:nil];
+ }
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+// Override UIViewController
+// Controls are not created immediately, properties must be set before the form
+// is displayed
+- (void)viewWillAppear:(BOOL)animated {
+ _host.text = _hostName;
+
+ [_switchAskAgain setOn:!_shouldPrompt];
+
+ // TODO (aboone) The switch is being hidden in all cases, this functionality
+ // is not scheduled for QA yet.
+ // if (!_pairingSupported) {
+ _switchAskAgain.hidden = YES;
+ _shouldSavePin.hidden = YES;
+ _switchAskAgain.enabled = NO;
+ //}
+ [_hostPin becomeFirstResponder];
+}
+
+// @protocol UITextFieldDelegate, called when the 'enter' key is pressed
+- (BOOL)textFieldShouldReturn:(UITextField*)textField {
+ [textField resignFirstResponder];
+ if (textField == _hostPin)
+ [self buttonConnectClicked:self];
+ return YES;
+}
+
+- (IBAction)buttonCancelClicked:(id)sender {
+ [_delegate cancelledConnectToHostWithPin:self];
+}
+
+- (IBAction)buttonConnectClicked:(id)sender {
+ [_delegate connectToHostWithPin:self
+ hostPin:_hostPin.text
+ shouldPrompt:!_switchAskAgain.isOn];
+}
+
+@end
diff --git a/remoting/ios/ui/pin_entry_view_controller_ipad.xib b/remoting/ios/ui/pin_entry_view_controller_ipad.xib
new file mode 100644
index 0000000000..6846c811bc
--- /dev/null
+++ b/remoting/ios/ui/pin_entry_view_controller_ipad.xib
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="5053" systemVersion="13C64" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PinEntryViewController">
+ <connections>
+ <outlet property="_cancelButton" destination="BKJ-ke-HyF" id="zYI-hk-6kg"/>
+ <outlet property="_connectButton" destination="Tf7-gd-ldS" id="xQf-zj-uJ9"/>
+ <outlet property="_controlView" destination="Cqg-ut-ayj" id="H7q-tt-WHK"/>
+ <outlet property="_host" destination="qjI-DX-ED7" id="vcr-tb-2Fe"/>
+ <outlet property="_hostPin" destination="c2o-Fx-DQH" id="A7i-R4-95W"/>
+ <outlet property="_shouldSavePin" destination="ZLq-E5-uGf" id="ade-Tz-kSo"/>
+ <outlet property="_switchAskAgain" destination="Bl9-pn-tsA" id="BxE-lI-u6t"/>
+ <outlet property="view" destination="2" id="3"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
+ <rect key="frame" x="0.0" y="0.0" width="768" height="1024"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <subviews>
+ <view contentMode="scaleAspectFit" translatesAutoresizingMaskIntoConstraints="NO" id="Cqg-ut-ayj">
+ <rect key="frame" x="192" y="127" width="384" height="205"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <subviews>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="&lt;hostname>" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qjI-DX-ED7">
+ <rect key="frame" x="142" y="20" width="225" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Enter the host's PIN" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VOw-pP-wKz">
+ <rect key="frame" x="20" y="49" width="239" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="PIN" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="c2o-Fx-DQH">
+ <rect key="frame" x="20" y="78" width="351" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="numberPad" returnKeyType="go" secureTextEntry="YES"/>
+ <connections>
+ <outlet property="delegate" destination="-1" id="jaH-uT-ejT"/>
+ </connections>
+ </textField>
+ <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Bl9-pn-tsA">
+ <rect key="frame" x="20" y="116" width="51" height="31"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </switch>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Don't ask in the future" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZLq-E5-uGf">
+ <rect key="frame" x="88" y="121" width="139" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="BKJ-ke-HyF">
+ <rect key="frame" x="20" y="155" width="160" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <state key="normal" title="Cancel">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="buttonCancelClicked:" destination="-1" eventType="touchUpInside" id="oxw-Oo-Npc"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Tf7-gd-ldS">
+ <rect key="frame" x="207" y="155" width="160" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <state key="normal" title="Connect">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="buttonConnectClicked:" destination="-1" eventType="touchUpInside" id="2Fi-pu-xH9"/>
+ </connections>
+ </button>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Authenticate to" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Abs-bA-a7i">
+ <rect key="frame" x="20" y="20" width="124" height="21"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="205" id="Z7v-Xg-IQu"/>
+ <constraint firstAttribute="width" constant="384" id="clt-7j-cb7"/>
+ </constraints>
+ </view>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstAttribute="centerX" secondItem="Cqg-ut-ayj" secondAttribute="centerX" id="Gs7-u0-yxZ"/>
+ <constraint firstItem="Cqg-ut-ayj" firstAttribute="top" secondItem="2" secondAttribute="top" constant="127" id="ivh-U7-Oc1"/>
+ </constraints>
+ <simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>
+ <simulatedScreenMetrics key="simulatedDestinationMetrics"/>
+ </view>
+ </objects>
+</document>
diff --git a/remoting/ios/ui/pin_entry_view_controller_iphone.xib b/remoting/ios/ui/pin_entry_view_controller_iphone.xib
new file mode 100644
index 0000000000..f7bb2a2a11
--- /dev/null
+++ b/remoting/ios/ui/pin_entry_view_controller_iphone.xib
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="5053" systemVersion="13C64" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3733"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PinEntryViewController">
+ <connections>
+ <outlet property="_cancelButton" destination="2Vw-9K-cVY" id="1wb-If-df2"/>
+ <outlet property="_connectButton" destination="NLw-jM-z2p" id="q5b-w6-cxk"/>
+ <outlet property="_host" destination="iat-rb-As1" id="azU-LC-CEu"/>
+ <outlet property="_hostPin" destination="Uow-Fu-2Yx" id="8iF-9q-f4R"/>
+ <outlet property="_shouldSavePin" destination="OPh-84-JII" id="Zby-0g-zE0"/>
+ <outlet property="_switchAskAgain" destination="5pF-pi-Stf" id="Ny5-lv-bsh"/>
+ <outlet property="view" destination="2" id="3"/>
+ </connections>
+ </placeholder>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="2">
+ <rect key="frame" x="0.0" y="0.0" width="320" height="480"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <subviews>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="&lt;hostname>" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iat-rb-As1">
+ <rect key="frame" x="155" y="104" width="70" height="15"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter the host's PIN" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="a63-kY-SHe">
+ <rect key="frame" x="67" y="127" width="112" height="15"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="PIN" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Uow-Fu-2Yx">
+ <rect key="frame" x="67" y="150" width="115" height="30"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="115" id="1Yc-ng-yRZ"/>
+ <constraint firstAttribute="height" constant="30" id="BCY-xe-HQx"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="numberPad" returnKeyType="go" secureTextEntry="YES"/>
+ <connections>
+ <outlet property="delegate" destination="-1" id="eEn-tS-i45"/>
+ </connections>
+ </textField>
+ <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5pF-pi-Stf">
+ <rect key="frame" x="190" y="149" width="51" height="31"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ </switch>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Save PIN" textAlignment="center" lineBreakMode="wordWrap" numberOfLines="2" minimumFontSize="7" adjustsLetterSpacingToFitWidth="YES" preferredMaxLayoutWidth="56" translatesAutoresizingMaskIntoConstraints="NO" id="OPh-84-JII">
+ <rect key="frame" x="247" y="157" width="56" height="16"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="13"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2Vw-9K-cVY">
+ <rect key="frame" x="79" y="188" width="45" height="31"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <state key="normal" title="Cancel">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="buttonCancelClicked:" destination="-1" eventType="touchUpInside" id="a0p-ci-esP"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NLw-jM-z2p">
+ <rect key="frame" x="155" y="189" width="55" height="29"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <state key="normal" title="Connect">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="buttonConnectClicked:" destination="-1" eventType="touchUpInside" id="CG9-X4-tEa"/>
+ </connections>
+ </button>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Authenticate to" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="G1n-4q-Knh">
+ <rect key="frame" x="67" y="104" width="85" height="15"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
+ <constraints>
+ <constraint firstItem="NLw-jM-z2p" firstAttribute="top" secondItem="Uow-Fu-2Yx" secondAttribute="bottom" constant="9" id="3jN-pC-2rt"/>
+ <constraint firstAttribute="centerY" secondItem="Uow-Fu-2Yx" secondAttribute="centerY" constant="75" id="5pL-cU-39Z"/>
+ <constraint firstItem="5pF-pi-Stf" firstAttribute="leading" secondItem="Uow-Fu-2Yx" secondAttribute="trailing" constant="8" id="9Qh-pj-al1"/>
+ <constraint firstItem="a63-kY-SHe" firstAttribute="top" secondItem="G1n-4q-Knh" secondAttribute="bottom" constant="8" id="Aep-CT-GtV"/>
+ <constraint firstItem="Uow-Fu-2Yx" firstAttribute="top" secondItem="a63-kY-SHe" secondAttribute="bottom" constant="8" id="Af6-MG-6hM"/>
+ <constraint firstItem="Uow-Fu-2Yx" firstAttribute="centerY" secondItem="5pF-pi-Stf" secondAttribute="centerY" id="Cxm-6Z-rBa"/>
+ <constraint firstAttribute="centerX" secondItem="Uow-Fu-2Yx" secondAttribute="centerX" constant="36" id="L6n-kv-1cb"/>
+ <constraint firstItem="NLw-jM-z2p" firstAttribute="leading" secondItem="2Vw-9K-cVY" secondAttribute="trailing" constant="31" id="OGl-yE-cFq"/>
+ <constraint firstItem="2Vw-9K-cVY" firstAttribute="leading" secondItem="Uow-Fu-2Yx" secondAttribute="leading" constant="12" id="Onp-Z7-Xp2"/>
+ <constraint firstItem="iat-rb-As1" firstAttribute="centerY" secondItem="G1n-4q-Knh" secondAttribute="centerY" id="RI9-Jx-K5Z"/>
+ <constraint firstItem="iat-rb-As1" firstAttribute="leading" secondItem="G1n-4q-Knh" secondAttribute="trailing" constant="3" id="XQd-6a-62O"/>
+ <constraint firstItem="NLw-jM-z2p" firstAttribute="centerY" secondItem="2Vw-9K-cVY" secondAttribute="centerY" id="baU-9W-Ab2"/>
+ <constraint firstItem="2Vw-9K-cVY" firstAttribute="top" secondItem="Uow-Fu-2Yx" secondAttribute="bottom" constant="8" id="dCn-aX-MNJ"/>
+ <constraint firstItem="a63-kY-SHe" firstAttribute="leading" secondItem="Uow-Fu-2Yx" secondAttribute="leading" id="ddk-qx-Ldm"/>
+ <constraint firstItem="Uow-Fu-2Yx" firstAttribute="centerY" secondItem="OPh-84-JII" secondAttribute="centerY" id="fwM-yD-GQh"/>
+ <constraint firstItem="5pF-pi-Stf" firstAttribute="centerY" secondItem="OPh-84-JII" secondAttribute="centerY" id="jfD-pi-EWm"/>
+ <constraint firstItem="OPh-84-JII" firstAttribute="leading" secondItem="5pF-pi-Stf" secondAttribute="trailing" constant="8" id="qjc-e2-rkS"/>
+ <constraint firstItem="a63-kY-SHe" firstAttribute="leading" secondItem="G1n-4q-Knh" secondAttribute="leading" id="tIh-JU-ubp"/>
+ </constraints>
+ <simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="lightContent"/>
+ <simulatedScreenMetrics key="simulatedDestinationMetrics"/>
+ </view>
+ </objects>
+</document>
diff --git a/remoting/ios/ui/scene_view.h b/remoting/ios/ui/scene_view.h
new file mode 100644
index 0000000000..8f082ff991
--- /dev/null
+++ b/remoting/ios/ui/scene_view.h
@@ -0,0 +1,171 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UI_SCENE_VIEW_H_
+#define REMOTING_IOS_UI_SCENE_VIEW_H_
+
+#import <Foundation/Foundation.h>
+#import <GLKit/GLKit.h>
+
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+typedef struct {
+ bool left;
+ bool right;
+ bool top;
+ bool bottom;
+} AnchorPosition;
+
+typedef struct {
+ int left;
+ int right;
+ int top;
+ int bottom;
+} MarginQuad;
+
+typedef struct {
+ CGPoint geometryVertex;
+ CGPoint textureVertex;
+} TexturedVertex;
+
+typedef struct {
+ TexturedVertex bl;
+ TexturedVertex br;
+ TexturedVertex tl;
+ TexturedVertex tr;
+} TexturedQuad;
+
+@interface SceneView : NSObject {
+ @private
+
+ // GL name
+ GLuint _textureId;
+
+ GLKMatrix4 _projectionMatrix;
+ GLKMatrix4 _modelViewMatrix;
+
+ // The draw surface is a triangle strip (triangles defined by the intersecting
+ // vertexes) to create a rectangle surface.
+ // 1****3
+ // | / |
+ // | / |
+ // 2****4
+ // This also determines the resolution of our surface, being a unit (NxN) grid
+ // with finite divisions. For our surface N = 1, and the number of divisions
+ // respects the CLIENT's desktop resolution.
+ TexturedQuad _glQuad;
+
+ // Cache of the CLIENT's desktop resolution.
+ webrtc::DesktopSize _contentSize;
+ // Cache of the HOST's desktop resolution.
+ webrtc::DesktopSize _frameSize;
+
+ // Location of the mouse according to the CLIENT in the prospective of the
+ // HOST resolution
+ webrtc::DesktopVector _mousePosition;
+
+ // When a user pans they expect the view to experience acceleration after
+ // they release the pan gesture. We track that velocity vector as a position
+ // delta factored over the frame rate of the GL Context. Velocity is
+ // accounted as a float.
+ CGPoint _panVelocity;
+}
+
+// The position of the scene is tracked in the prospective of the CLIENT
+// resolution. The Z-axis is used to track the scale of the render, our scene
+// never changes position on the Z-axis.
+@property(nonatomic, readonly) GLKVector3 position;
+
+// Space around border consumed by non-scene elements, we can not draw here
+@property(nonatomic, readonly) MarginQuad margin;
+
+@property(nonatomic, readonly) AnchorPosition anchored;
+
+- (const GLKMatrix4&)projectionMatrix;
+
+// calculate and return the current model view matrix
+- (const GLKMatrix4&)modelViewMatrix;
+
+- (const webrtc::DesktopSize&)contentSize;
+
+// Update the CLIENT resolution and draw scene size, accounting for margins
+- (void)setContentSize:(const CGSize&)size;
+
+- (const webrtc::DesktopSize&)frameSize;
+
+// Update the HOST resolution and reinitialize the scene positioning
+- (void)setFrameSize:(const webrtc::DesktopSize&)size;
+
+- (const webrtc::DesktopVector&)mousePosition;
+
+- (void)setPanVelocity:(const CGPoint&)delta;
+
+- (void)setMarginsFromLeft:(int)left
+ right:(int)right
+ top:(int)top
+ bottom:(int)bottom;
+
+// Draws to a GL Context
+- (void)draw;
+
+- (BOOL)containsTouchPoint:(CGPoint)point;
+
+// Applies translation and zoom. Translation is bounded to screen edges.
+// Zooming is bounded on the lower side to the maximum of width and height, and
+// on the upper side by a constant, experimentally chosen.
+- (void)panAndZoom:(CGPoint)translation scaleBy:(float)scale;
+
+// Mouse is tracked in the perspective of the HOST desktop, but the projection
+// to the user is in the perspective of the CLIENT resolution. Find the HOST
+// position that is the center of the current CLIENT view. If the mouse is in
+// the half of the CLIENT screen that is closest to an anchor, then move the
+// mouse, otherwise the mouse should be centered.
+- (void)updateMousePositionAndAnchorsWithTranslation:(CGPoint)translation
+ scale:(float)scale;
+
+// When zoom is changed the scene is translated to keep an anchored point
+// (an anchored edge, or the spot the user is touching) at the same place in the
+// User's perspective. Return the delta of the position of the lower endpoint
+// of the axis
++ (float)positionDeltaFromScaling:(float)ratio
+ position:(float)position
+ length:(float)length
+ anchor:(float)anchor;
+
+// Return the delta of the position of the lower endpoint of the axis
++ (int)positionDeltaFromTranslation:(int)translation
+ position:(int)position
+ freeSpace:(int)freeSpace
+ scaleingPositionDelta:(int)scaleingPositionDelta
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh;
+
+// |position + delta| is snapped to the bounds, return the delta in respect to
+// the bounding.
++ (int)boundDeltaFromPosition:(float)position
+ delta:(int)delta
+ lowerBound:(int)lowerBound
+ upperBound:(int)upperBound;
+
+// Return |nextPosition| when it is anchored and still in the respective 1/2 of
+// the screen. When |nextPosition| is outside scene's edge, snap to edge.
+// Otherwise return |centerPosition|
++ (int)boundMouseGivenNextPosition:(int)nextPosition
+ maxPosition:(int)maxPosition
+ centerPosition:(int)centerPosition
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh;
+
+// If the mouse is at an edge return zero, otherwise return |velocity|
++ (float)boundVelocity:(float)velocity
+ axisLength:(int)axisLength
+ mousePosition:(int)mousePosition;
+
+// Update the scene acceleration vector.
+// Returns true if velocity before 'ticking' is non-zero.
+- (BOOL)tickPanVelocity;
+
+@end
+
+#endif // REMOTING_IOS_UI_SCENE_VIEW_H_ \ No newline at end of file
diff --git a/remoting/ios/ui/scene_view.mm b/remoting/ios/ui/scene_view.mm
new file mode 100644
index 0000000000..97a577df77
--- /dev/null
+++ b/remoting/ios/ui/scene_view.mm
@@ -0,0 +1,642 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/scene_view.h"
+
+#import "remoting/ios/utility.h"
+
+namespace {
+
+// TODO (aboone) Some of the layout is not yet set in stone, so variables have
+// been used to position and turn items on and off. Eventually these may be
+// stabilized and removed.
+
+// Scroll speed multiplier for swiping
+const static int kMouseSensitivity = 2.5;
+
+// Input Axis inversion
+// 1 for standard, -1 for inverted
+const static int kXAxisInversion = -1;
+const static int kYAxisInversion = -1;
+
+// Experimental value for bounding the maximum zoom ratio
+const static int kMaxZoomSize = 3;
+} // namespace
+
+@interface SceneView (Private)
+// Returns the number of pixels displayed per device pixel when the scaling is
+// such that the entire frame would fit perfectly in content. Note the ratios
+// are different for width and height, some people have multiple monitors, some
+// have 16:9 or 4:3 while iPad is always single screen, but different iOS
+// devices have different resolutions.
+- (CGPoint)pixelRatio;
+
+// Return the FrameSize in perspective of the CLIENT resolution
+- (webrtc::DesktopSize)frameSizeToScale:(float)scale;
+
+// When bounded on the top and right, this point is where the scene must be
+// positioned given a scene size
+- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size;
+
+// Converts a point in the the CLIENT resolution to a similar point in the HOST
+// resolution. Additionally, CLIENT resolution is expressed in float values
+// while HOST operates in integer values.
+- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint
+ targetPoint:(webrtc::DesktopVector&)desktopPoint;
+
+// Converts a point in the the HOST resolution to a similar point in the CLIENT
+// resolution. Additionally, CLIENT resolution is expressed in float values
+// while HOST operates in integer values.
+- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint
+ targetPoint:(CGPoint&)touchPoint;
+@end
+
+@implementation SceneView
+
+- (id)init {
+ self = [super init];
+ if (self) {
+
+ _frameSize = webrtc::DesktopSize(1, 1);
+ _contentSize = webrtc::DesktopSize(1, 1);
+ _mousePosition = webrtc::DesktopVector(0, 0);
+
+ _position = GLKVector3Make(0, 0, 1);
+ _margin.left = 0;
+ _margin.right = 0;
+ _margin.top = 0;
+ _margin.bottom = 0;
+ _anchored.left = false;
+ _anchored.right = false;
+ _anchored.top = false;
+ _anchored.bottom = false;
+ }
+ return self;
+}
+
+- (const GLKMatrix4&)projectionMatrix {
+ return _projectionMatrix;
+}
+
+- (const GLKMatrix4&)modelViewMatrix {
+ // Start by using the entire scene
+ _modelViewMatrix = GLKMatrix4Identity;
+
+ // Position scene according to any panning or bounds
+ _modelViewMatrix = GLKMatrix4Translate(_modelViewMatrix,
+ _position.x + _margin.left,
+ _position.y + _margin.bottom,
+ 0.0);
+
+ // Apply zoom
+ _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix,
+ _position.z / self.pixelRatio.x,
+ _position.z / self.pixelRatio.y,
+ 1.0);
+
+ // We are directly above the screen and looking down.
+ static const GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt(
+ 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // center view
+
+ _modelViewMatrix = GLKMatrix4Multiply(viewMatrix, _modelViewMatrix);
+
+ return _modelViewMatrix;
+}
+
+- (const webrtc::DesktopSize&)contentSize {
+ return _contentSize;
+}
+
+- (void)setContentSize:(const CGSize&)size {
+
+ _contentSize.set(size.width, size.height);
+
+ _projectionMatrix = GLKMatrix4MakeOrtho(
+ 0.0, _contentSize.width(), 0.0, _contentSize.height(), 1.0, -1.0);
+
+ TexturedQuad newQuad;
+ newQuad.bl.geometryVertex = CGPointMake(0.0, 0.0);
+ newQuad.br.geometryVertex = CGPointMake(_contentSize.width(), 0.0);
+ newQuad.tl.geometryVertex = CGPointMake(0.0, _contentSize.height());
+ newQuad.tr.geometryVertex =
+ CGPointMake(_contentSize.width(), _contentSize.height());
+
+ newQuad.bl.textureVertex = CGPointMake(0.0, 1.0);
+ newQuad.br.textureVertex = CGPointMake(1.0, 1.0);
+ newQuad.tl.textureVertex = CGPointMake(0.0, 0.0);
+ newQuad.tr.textureVertex = CGPointMake(1.0, 0.0);
+
+ _glQuad = newQuad;
+}
+
+- (const webrtc::DesktopSize&)frameSize {
+ return _frameSize;
+}
+
+- (void)setFrameSize:(const webrtc::DesktopSize&)size {
+ DCHECK(size.width() > 0 && size.height() > 0);
+ // Don't do anything if the size has not changed.
+ if (_frameSize.equals(size))
+ return;
+
+ _frameSize.set(size.width(), size.height());
+
+ _position.x = 0;
+ _position.y = 0;
+
+ float verticalPixelScaleRatio =
+ (static_cast<float>(_contentSize.height() - _margin.top -
+ _margin.bottom) /
+ static_cast<float>(_frameSize.height())) /
+ _position.z;
+
+ // Anchored at the position (0,0)
+ _anchored.left = YES;
+ _anchored.right = NO;
+ _anchored.top = NO;
+ _anchored.bottom = YES;
+
+ [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:verticalPixelScaleRatio];
+
+ // Center the mouse on the CLIENT screen
+ webrtc::DesktopVector centerMouseLocation;
+ if ([self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2,
+ _contentSize.height() / 2)
+ targetPoint:centerMouseLocation]) {
+ _mousePosition.set(centerMouseLocation.x(), centerMouseLocation.y());
+ }
+
+#if DEBUG
+ NSLog(@"resized frame:%d:%d scale:%f",
+ _frameSize.width(),
+ _frameSize.height(),
+ _position.z);
+#endif // DEBUG
+}
+
+- (const webrtc::DesktopVector&)mousePosition {
+ return _mousePosition;
+}
+
+- (void)setPanVelocity:(const CGPoint&)delta {
+ _panVelocity.x = delta.x;
+ _panVelocity.y = delta.y;
+}
+
+- (void)setMarginsFromLeft:(int)left
+ right:(int)right
+ top:(int)top
+ bottom:(int)bottom {
+ _margin.left = left;
+ _margin.right = right;
+ _margin.top = top;
+ _margin.bottom = bottom;
+}
+
+- (void)draw {
+ glEnableVertexAttribArray(GLKVertexAttribPosition);
+ glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
+ glEnableVertexAttribArray(GLKVertexAttribTexCoord1);
+
+ // Define our scene space
+ glVertexAttribPointer(GLKVertexAttribPosition,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.geometryVertex));
+ // Define the desktop plane
+ glVertexAttribPointer(GLKVertexAttribTexCoord0,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.textureVertex));
+ // Define the cursor plane
+ glVertexAttribPointer(GLKVertexAttribTexCoord1,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(TexturedVertex),
+ &(_glQuad.bl.textureVertex));
+
+ // Draw!
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+ [Utility logGLErrorCode:@"SceneView draw"];
+}
+
+- (CGPoint)pixelRatio {
+
+ CGPoint r = CGPointMake(static_cast<float>(_contentSize.width()) /
+ static_cast<float>(_frameSize.width()),
+ static_cast<float>(_contentSize.height()) /
+ static_cast<float>(_frameSize.height()));
+ return r;
+}
+
+- (webrtc::DesktopSize)frameSizeToScale:(float)scale {
+ return webrtc::DesktopSize(_frameSize.width() * scale,
+ _frameSize.height() * scale);
+}
+
+- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size {
+ webrtc::DesktopVector r(
+ _contentSize.width() - _margin.left - _margin.right - size.width(),
+ _contentSize.height() - _margin.bottom - _margin.top - size.height());
+
+ if (r.x() > 0) {
+ r.set((_contentSize.width() - size.width()) / 2, r.y());
+ }
+
+ if (r.y() > 0) {
+ r.set(r.x(), (_contentSize.height() - size.height()) / 2);
+ }
+
+ return r;
+}
+
+- (BOOL)containsTouchPoint:(CGPoint)point {
+ // Here frame is from the top-left corner, most other calculations are framed
+ // from the bottom left.
+ CGRect frame =
+ CGRectMake(_margin.left,
+ _margin.top,
+ _contentSize.width() - _margin.left - _margin.right,
+ _contentSize.height() - _margin.top - _margin.bottom);
+ return CGRectContainsPoint(frame, point);
+}
+
+- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint
+ targetPoint:(webrtc::DesktopVector&)mousePoint {
+ if (![self containsTouchPoint:touchPoint]) {
+ return NO;
+ }
+ // A touch location occurs in respect to the user's entire view surface.
+
+ // The GL Context is upside down from the User's perspective so flip it.
+ CGPoint glOrientedTouchPoint =
+ CGPointMake(touchPoint.x, _contentSize.height() - touchPoint.y);
+
+ // The GL surface generally is not at the same origination point as the touch,
+ // so translate by the scene's position.
+ CGPoint glOrientedPointInRespectToFrame =
+ CGPointMake(glOrientedTouchPoint.x - _position.x,
+ glOrientedTouchPoint.y - _position.y);
+
+ // The perspective exists in relative to the CLIENT resolution at 1:1, zoom
+ // our perspective so we are relative to the HOST at 1:1
+ CGPoint glOrientedPointInFrame =
+ CGPointMake(glOrientedPointInRespectToFrame.x / _position.z,
+ glOrientedPointInRespectToFrame.y / _position.z);
+
+ // Finally, flip the perspective back over to the Users, but this time in
+ // respect to the HOST desktop. Floor to ensure the result is always in
+ // frame.
+ CGPoint deskTopOrientedPointInFrame =
+ CGPointMake(floorf(glOrientedPointInFrame.x),
+ floorf(_frameSize.height() - glOrientedPointInFrame.y));
+
+ // Convert from float to integer
+ mousePoint.set(deskTopOrientedPointInFrame.x, deskTopOrientedPointInFrame.y);
+
+ return CGRectContainsPoint(
+ CGRectMake(0, 0, _frameSize.width(), _frameSize.height()),
+ deskTopOrientedPointInFrame);
+}
+
+- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint
+ targetPoint:(CGPoint&)touchPoint {
+ // A mouse point is in respect to the desktop frame.
+
+ // Flip the perspective back over to the Users, in
+ // respect to the HOST desktop.
+ CGPoint deskTopOrientedPointInFrame =
+ CGPointMake(mousePoint.x(), _frameSize.height() - mousePoint.y());
+
+ // The perspective exists in relative to the CLIENT resolution at 1:1, zoom
+ // our perspective so we are relative to the HOST at 1:1
+ CGPoint glOrientedPointInFrame =
+ CGPointMake(deskTopOrientedPointInFrame.x * _position.z,
+ deskTopOrientedPointInFrame.y * _position.z);
+
+ // The GL surface generally is not at the same origination point as the touch,
+ // so translate by the scene's position.
+ CGPoint glOrientedPointInRespectToFrame =
+ CGPointMake(glOrientedPointInFrame.x + _position.x,
+ glOrientedPointInFrame.y + _position.y);
+
+ // Convert from float to integer
+ touchPoint.x = floorf(glOrientedPointInRespectToFrame.x);
+ touchPoint.y = floorf(glOrientedPointInRespectToFrame.y);
+
+ return [self containsTouchPoint:touchPoint];
+}
+
+- (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio {
+ CGPoint ratios = [self pixelRatio];
+
+ // New Scaling factor bounded by a min and max
+ float resultScale = _position.z * ratio;
+ float scaleUpperBound = MAX(ratios.x, MAX(ratios.y, kMaxZoomSize));
+ float scaleLowerBound = MIN(ratios.x, ratios.y);
+
+ if (resultScale < scaleLowerBound) {
+ resultScale = scaleLowerBound;
+ } else if (resultScale > scaleUpperBound) {
+ resultScale = scaleUpperBound;
+ }
+
+ DCHECK(isnormal(resultScale) && resultScale > 0);
+
+ // The GL perspective is upside down in relation to the User's view, so flip
+ // the translation
+ translation.y = -translation.y;
+
+ // The constants here could be user options later.
+ translation.x =
+ translation.x * kXAxisInversion * (1 / (ratios.x * kMouseSensitivity));
+ translation.y =
+ translation.y * kYAxisInversion * (1 / (ratios.y * kMouseSensitivity));
+
+ CGPoint delta = CGPointMake(0, 0);
+ CGPoint scaleDelta = CGPointMake(0, 0);
+
+ webrtc::DesktopSize currentSize = [self frameSizeToScale:_position.z];
+
+ {
+ // Closure for this variable, so the variable is not available to the rest
+ // of this function
+ webrtc::DesktopVector currentBounds = [self getBoundsForSize:currentSize];
+ // There are rounding errors in the scope of this function, see the
+ // butterfly effect. In successive calls, the resulting position isn't
+ // always exactly the calculated position. If we know we are Anchored, then
+ // go ahead and reposition it to the values above.
+ if (_anchored.right) {
+ _position.x = currentBounds.x();
+ }
+
+ if (_anchored.top) {
+ _position.y = currentBounds.y();
+ }
+ }
+
+ if (_position.z != resultScale) {
+ // When scaling the scene, the origination of scaling is the mouse's
+ // location. But when the frame is anchored, adjust the origination to the
+ // anchor point.
+
+ CGPoint mousePositionInClientResolution;
+ [self convertMousePointToTouchPoint:_mousePosition
+ targetPoint:mousePositionInClientResolution];
+
+ // Prefer to zoom based on the left anchor when there is a choice
+ if (_anchored.left) {
+ mousePositionInClientResolution.x = 0;
+ } else if (_anchored.right) {
+ mousePositionInClientResolution.x = _contentSize.width();
+ }
+
+ // Prefer to zoom out from the top anchor when there is a choice
+ if (_anchored.top) {
+ mousePositionInClientResolution.y = _contentSize.height();
+ } else if (_anchored.bottom) {
+ mousePositionInClientResolution.y = 0;
+ }
+
+ scaleDelta.x -=
+ [SceneView positionDeltaFromScaling:ratio
+ position:_position.x
+ length:currentSize.width()
+ anchor:mousePositionInClientResolution.x];
+
+ scaleDelta.y -=
+ [SceneView positionDeltaFromScaling:ratio
+ position:_position.y
+ length:currentSize.height()
+ anchor:mousePositionInClientResolution.y];
+ }
+
+ delta.x = [SceneView
+ positionDeltaFromTranslation:translation.x
+ position:_position.x
+ freeSpace:_contentSize.width() - currentSize.width()
+ scaleingPositionDelta:scaleDelta.x
+ isAnchoredLow:_anchored.left
+ isAnchoredHigh:_anchored.right];
+
+ delta.y = [SceneView
+ positionDeltaFromTranslation:translation.y
+ position:_position.y
+ freeSpace:_contentSize.height() - currentSize.height()
+ scaleingPositionDelta:scaleDelta.y
+ isAnchoredLow:_anchored.bottom
+ isAnchoredHigh:_anchored.top];
+ {
+ // Closure for this variable, so the variable is not available to the rest
+ // of this function
+ webrtc::DesktopVector bounds =
+ [self getBoundsForSize:[self frameSizeToScale:resultScale]];
+
+ delta.x = [SceneView boundDeltaFromPosition:_position.x
+ delta:delta.x
+ lowerBound:bounds.x()
+ upperBound:0];
+
+ delta.y = [SceneView boundDeltaFromPosition:_position.y
+ delta:delta.y
+ lowerBound:bounds.y()
+ upperBound:0];
+ }
+
+ BOOL isLeftAndRightAnchored = _anchored.left && _anchored.right;
+ BOOL isTopAndBottomAnchored = _anchored.top && _anchored.bottom;
+
+ [self updateMousePositionAndAnchorsWithTranslation:translation
+ scale:resultScale];
+
+ // If both anchors were lost, then keep the one that is easier to predict
+ if (isLeftAndRightAnchored && !_anchored.left && !_anchored.right) {
+ delta.x = -_position.x;
+ _anchored.left = YES;
+ }
+
+ // If both anchors were lost, then keep the one that is easier to predict
+ if (isTopAndBottomAnchored && !_anchored.top && !_anchored.bottom) {
+ delta.y = -_position.y;
+ _anchored.bottom = YES;
+ }
+
+ // FINALLY, update the scene's position
+ _position.x += delta.x;
+ _position.y += delta.y;
+ _position.z = resultScale;
+}
+
+- (void)updateMousePositionAndAnchorsWithTranslation:(CGPoint)translation
+ scale:(float)scale {
+ webrtc::DesktopVector centerMouseLocation;
+ [self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2,
+ _contentSize.height() / 2)
+ targetPoint:centerMouseLocation];
+
+ webrtc::DesktopVector currentBounds =
+ [self getBoundsForSize:[self frameSizeToScale:_position.z]];
+ webrtc::DesktopVector nextBounds =
+ [self getBoundsForSize:[self frameSizeToScale:scale]];
+
+ webrtc::DesktopVector predictedMousePosition(
+ _mousePosition.x() - translation.x, _mousePosition.y() + translation.y);
+
+ _mousePosition.set(
+ [SceneView boundMouseGivenNextPosition:predictedMousePosition.x()
+ maxPosition:_frameSize.width()
+ centerPosition:centerMouseLocation.x()
+ isAnchoredLow:_anchored.left
+ isAnchoredHigh:_anchored.right],
+ [SceneView boundMouseGivenNextPosition:predictedMousePosition.y()
+ maxPosition:_frameSize.height()
+ centerPosition:centerMouseLocation.y()
+ isAnchoredLow:_anchored.top
+ isAnchoredHigh:_anchored.bottom]);
+
+ _panVelocity.x = [SceneView boundVelocity:_panVelocity.x
+ axisLength:_frameSize.width()
+ mousePosition:_mousePosition.x()];
+ _panVelocity.y = [SceneView boundVelocity:_panVelocity.y
+ axisLength:_frameSize.height()
+ mousePosition:_mousePosition.y()];
+
+ _anchored.left = (nextBounds.x() >= 0) ||
+ (_position.x == 0 &&
+ predictedMousePosition.x() <= centerMouseLocation.x());
+
+ _anchored.right =
+ (nextBounds.x() >= 0) ||
+ (_position.x == currentBounds.x() &&
+ predictedMousePosition.x() >= centerMouseLocation.x()) ||
+ (_mousePosition.x() == _frameSize.width() - 1 && !_anchored.left);
+
+ _anchored.bottom = (nextBounds.y() >= 0) ||
+ (_position.y == 0 &&
+ predictedMousePosition.y() >= centerMouseLocation.y());
+
+ _anchored.top =
+ (nextBounds.y() >= 0) ||
+ (_position.y == currentBounds.y() &&
+ predictedMousePosition.y() <= centerMouseLocation.y()) ||
+ (_mousePosition.y() == _frameSize.height() - 1 && !_anchored.bottom);
+}
+
++ (float)positionDeltaFromScaling:(float)ratio
+ position:(float)position
+ length:(float)length
+ anchor:(float)anchor {
+ float newSize = length * ratio;
+ float scaleXBy = fabs(position - anchor) / length;
+ float delta = (newSize - length) * scaleXBy;
+ return delta;
+}
+
++ (int)positionDeltaFromTranslation:(int)translation
+ position:(int)position
+ freeSpace:(int)freeSpace
+ scaleingPositionDelta:(int)scaleingPositionDelta
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh {
+ if (isAnchoredLow && isAnchoredHigh) {
+ // center the view
+ return (freeSpace / 2) - position;
+ } else if (isAnchoredLow) {
+ return 0;
+ } else if (isAnchoredHigh) {
+ return scaleingPositionDelta;
+ } else {
+ return translation + scaleingPositionDelta;
+ }
+}
+
++ (int)boundDeltaFromPosition:(float)position
+ delta:(int)delta
+ lowerBound:(int)lowerBound
+ upperBound:(int)upperBound {
+ int result = position + delta;
+
+ if (lowerBound < upperBound) { // the view is larger than the bounds
+ if (result > upperBound) {
+ result = upperBound;
+ } else if (result < lowerBound) {
+ result = lowerBound;
+ }
+ } else {
+ // the view is smaller than the bounds so we'll always be at the lowerBound
+ result = lowerBound;
+ }
+ return result - position;
+}
+
++ (int)boundMouseGivenNextPosition:(int)nextPosition
+ maxPosition:(int)maxPosition
+ centerPosition:(int)centerPosition
+ isAnchoredLow:(BOOL)isAnchoredLow
+ isAnchoredHigh:(BOOL)isAnchoredHigh {
+ if (nextPosition < 0) {
+ return 0;
+ }
+ if (nextPosition > maxPosition - 1) {
+ return maxPosition - 1;
+ }
+
+ if ((isAnchoredLow && nextPosition <= centerPosition) ||
+ (isAnchoredHigh && nextPosition >= centerPosition)) {
+ return nextPosition;
+ }
+
+ return centerPosition;
+}
+
++ (float)boundVelocity:(float)velocity
+ axisLength:(int)axisLength
+ mousePosition:(int)mousePosition {
+ if (velocity != 0) {
+ if (mousePosition <= 0 || mousePosition >= (axisLength - 1)) {
+ return 0;
+ }
+ }
+
+ return velocity;
+}
+
+- (BOOL)tickPanVelocity {
+ BOOL inMotion = ((_panVelocity.x != 0.0) || (_panVelocity.y != 0.0));
+
+ if (inMotion) {
+
+ uint32_t divisor = 50 / _position.z;
+ float reducer = .95;
+
+ if (_panVelocity.x != 0.0 && ABS(_panVelocity.x) < divisor) {
+ _panVelocity = CGPointMake(0.0, _panVelocity.y);
+ }
+
+ if (_panVelocity.y != 0.0 && ABS(_panVelocity.y) < divisor) {
+ _panVelocity = CGPointMake(_panVelocity.x, 0.0);
+ }
+
+ [self panAndZoom:CGPointMake(_panVelocity.x / divisor,
+ _panVelocity.y / divisor)
+ scaleBy:1.0];
+
+ _panVelocity.x *= reducer;
+ _panVelocity.y *= reducer;
+ }
+
+ return inMotion;
+}
+
+@end \ No newline at end of file
diff --git a/remoting/ios/ui/scene_view_unittest.mm b/remoting/ios/ui/scene_view_unittest.mm
new file mode 100644
index 0000000000..d1dfabcd7d
--- /dev/null
+++ b/remoting/ios/ui/scene_view_unittest.mm
@@ -0,0 +1,1219 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "remoting/ios/ui/scene_view.h"
+
+#import "base/compiler_specific.h"
+#import "testing/gtest_mac.h"
+
+namespace remoting {
+
+namespace {
+const int kClientWidth = 200;
+const int kClientHeight = 100;
+const webrtc::DesktopSize kClientSize(kClientWidth, kClientHeight);
+// Smaller then ClientSize
+const webrtc::DesktopSize kSmall(50, 75);
+// Inverted - The vertical is closer to an edge than the horizontal
+const webrtc::DesktopSize kSmallInversed(175, 50);
+// Larger then ClientSize
+const webrtc::DesktopSize kLarge(800, 125);
+const webrtc::DesktopSize kLargeInversed(225, 400);
+} // namespace
+
+class SceneViewTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ scene_ = [[SceneView alloc] init];
+ [scene_
+ setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ [scene_ setFrameSize:kLarge];
+ }
+
+ void MakeLarge() { [scene_ setFrameSize:kLarge]; }
+
+ SceneView* scene_;
+};
+
+TEST(SceneViewTest_Property, ContentSize) {
+ SceneView* scene = [[SceneView alloc] init];
+
+ [scene setContentSize:CGSizeMake(0, 0)];
+ EXPECT_EQ(0, scene.contentSize.width());
+ EXPECT_EQ(0, scene.contentSize.height());
+ float zeros[16] = {1.0f / 0.0f, 0, 0, 0, 0, 1.0f / 0.0f, 0, 0,
+ 0, 0, 1, 0, 0.0f / 0.0f, 0.0f / 0.0f, 0, 1};
+
+ ASSERT_TRUE(memcmp(zeros, scene.projectionMatrix.m, 16 * sizeof(float)) == 0);
+
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ EXPECT_EQ(kClientSize.width(), scene.contentSize.width());
+ EXPECT_EQ(kClientSize.height(), scene.contentSize.height());
+
+ EXPECT_TRUE(memcmp(GLKMatrix4MakeOrtho(
+ 0.0, kClientWidth, 0.0, kClientHeight, 1.0, -1.0).m,
+ scene.projectionMatrix.m,
+ 16 * sizeof(float)) == 0);
+}
+
+TEST(SceneViewTest_Property, FrameSizeInit) {
+ SceneView* scene = [[SceneView alloc] init];
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+
+ [scene setFrameSize:webrtc::DesktopSize(1, 1)];
+ EXPECT_EQ(1, scene.frameSize.width());
+ EXPECT_EQ(1, scene.frameSize.height());
+
+ EXPECT_EQ(0, scene.position.x);
+ EXPECT_EQ(0, scene.position.y);
+ EXPECT_EQ(1, scene.position.z);
+
+ EXPECT_FALSE(scene.anchored.left);
+ EXPECT_FALSE(scene.anchored.right);
+ EXPECT_FALSE(scene.anchored.top);
+ EXPECT_FALSE(scene.anchored.bottom);
+
+ EXPECT_EQ(0, scene.mousePosition.x());
+ EXPECT_EQ(0, scene.mousePosition.y());
+}
+
+TEST(SceneViewTest_Property, FrameSizeLarge) {
+ SceneView* scene = [[SceneView alloc] init];
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ [scene setFrameSize:kLarge];
+ EXPECT_EQ(kLarge.width(), scene.frameSize.width());
+ EXPECT_EQ(kLarge.height(), scene.frameSize.height());
+
+ // Screen is positioned in the lower,left corner, zoomed until the vertical
+ // fits exactly, and then centered horizontally
+ // HOST
+ // CLIENT ------------------------------------------------
+ // ------------ | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // ------------ ------------------------------------------------
+ // RESULT - ONSCREEN is completely covered, with some of the HOST off screen
+ // (-.-) the mouse cursor
+ // -----------------------------------------
+ // | ONSCREEN | OFFSCREEN |
+ // | -.- | |
+ // | | |
+ // -----------------------------------------
+ float scale = static_cast<float>(kClientSize.height()) /
+ static_cast<float>(kLarge.height());
+ // vertical fits exactly
+ EXPECT_EQ(scale, scene.position.z);
+
+ // sitting on both Axis
+ EXPECT_EQ(0, scene.position.x);
+ EXPECT_EQ(0, scene.position.y);
+
+ // bound on 3 sides, not on the right
+ EXPECT_TRUE(scene.anchored.left);
+ EXPECT_FALSE(scene.anchored.right);
+ EXPECT_TRUE(scene.anchored.top);
+ EXPECT_TRUE(scene.anchored.bottom);
+
+ // mouse is off center on the left horizontal
+ EXPECT_EQ(kClientSize.width() / (scale * 2), scene.mousePosition.x());
+ // mouse is centered vertical
+ EXPECT_EQ(kLarge.height() / 2, scene.mousePosition.y());
+}
+
+TEST(SceneViewTest_Property, FrameSizeLargeInversed) {
+ SceneView* scene = [[SceneView alloc] init];
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ [scene setFrameSize:kLargeInversed];
+ EXPECT_EQ(kLargeInversed.width(), scene.frameSize.width());
+ EXPECT_EQ(kLargeInversed.height(), scene.frameSize.height());
+
+ // Screen is positioned in the lower,left corner, zoomed until the vertical
+ // fits exactly, and then centered horizontally
+ // HOST
+ // ---------------
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // | |
+ // CLIENT | |
+ // ------------- | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // ------------- ---------------
+ // RESULT, entire HOST is on screen
+ // (-.-) the mouse cursor, XX is black backdrop
+ // -------------
+ // |XX| |XX|
+ // |XX| -.- |XX|
+ // |XX| |XX|
+ // -------------
+ float scale = static_cast<float>(kClientSize.height()) /
+ static_cast<float>(kLargeInversed.height());
+ // Vertical fits exactly
+ EXPECT_EQ(scale, scene.position.z);
+
+ // centered
+ EXPECT_EQ(
+ (kClientSize.width() - static_cast<int>(scale * kLargeInversed.width())) /
+ 2,
+ scene.position.x);
+ // sits on Axis
+ EXPECT_EQ(0, scene.position.y);
+
+ // bound on all 4 sides
+ EXPECT_TRUE(scene.anchored.left);
+ EXPECT_TRUE(scene.anchored.right);
+ EXPECT_TRUE(scene.anchored.top);
+ EXPECT_TRUE(scene.anchored.bottom);
+
+ // mouse is in centered both vertical and horizontal
+ EXPECT_EQ(kLargeInversed.width() / 2, scene.mousePosition.x());
+ EXPECT_EQ(kLargeInversed.height() / 2, scene.mousePosition.y());
+}
+
+TEST(SceneViewTest_Property, FrameSizeSmall) {
+ SceneView* scene = [[SceneView alloc] init];
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ [scene setFrameSize:kSmall];
+ EXPECT_EQ(kSmall.width(), scene.frameSize.width());
+ EXPECT_EQ(kSmall.height(), scene.frameSize.height());
+
+ // Screen is positioned in the lower,left corner, zoomed until the vertical
+ // fits exactly, and then centered horizontally
+ // CLIENT
+ // ---------------------------
+ // | | HOST
+ // | | -------
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // | | | |
+ // --------------------------- -------
+ // RESULT, entire HOST is on screen
+ // (-.-) the mouse cursor, XX is black backdrop
+ // ---------------------------
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // |XXXXXXXXX| -.- |XXXXXXXXX|
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // |XXXXXXXXX| |XXXXXXXXX|
+ // ---------------------------
+ float scale = static_cast<float>(kClientSize.height()) /
+ static_cast<float>(kSmall.height());
+ // Vertical fits exactly
+ EXPECT_EQ(scale, scene.position.z);
+
+ // centered
+ EXPECT_EQ(
+ (kClientSize.width() - static_cast<int>(scale * kSmall.width())) / 2,
+ scene.position.x);
+ // sits on Axis
+ EXPECT_EQ(0, scene.position.y);
+
+ // bound on all 4 sides
+ EXPECT_TRUE(scene.anchored.left);
+ EXPECT_TRUE(scene.anchored.right);
+ EXPECT_TRUE(scene.anchored.top);
+ EXPECT_TRUE(scene.anchored.bottom);
+
+ // mouse is in centered both vertical and horizontal
+ EXPECT_EQ((kSmall.width() / 2) - 1, // -1 for pixel rounding
+ scene.mousePosition.x());
+ EXPECT_EQ(kSmall.height() / 2, scene.mousePosition.y());
+}
+
+TEST(SceneViewTest_Property, FrameSizeSmallInversed) {
+ SceneView* scene = [[SceneView alloc] init];
+ [scene setContentSize:CGSizeMake(kClientSize.width(), kClientSize.height())];
+ [scene setFrameSize:kSmallInversed];
+ EXPECT_EQ(kSmallInversed.width(), scene.frameSize.width());
+ EXPECT_EQ(kSmallInversed.height(), scene.frameSize.height());
+
+ // Screen is positioned in the lower,left corner, zoomed until the vertical
+ // fits exactly, and then centered horizontally
+ // CLIENT
+ // ---------------------------
+ // | |
+ // | |
+ // | | HOST
+ // | | ----------------------
+ // | | | |
+ // | | | |
+ // | | | |
+ // --------------------------- ----------------------
+ // RESULT - ONSCREEN is completely covered, with some of the HOST off screen
+ // (-.-) the mouse cursor
+ // --------------------------------------------
+ // | ONSCREEN | OFFSCREEN |
+ // | | |
+ // | | |
+ // | -.- | |
+ // | | |
+ // | | |
+ // | | |
+ // --------------------------------------------
+ float scale = static_cast<float>(kClientSize.height()) /
+ static_cast<float>(kSmallInversed.height());
+ // vertical fits exactly
+ EXPECT_EQ(scale, scene.position.z);
+
+ // sitting on both Axis
+ EXPECT_EQ(0, scene.position.x);
+ EXPECT_EQ(0, scene.position.y);
+
+ // bound on 3 sides, not on the right
+ EXPECT_TRUE(scene.anchored.left);
+ EXPECT_FALSE(scene.anchored.right);
+ EXPECT_TRUE(scene.anchored.top);
+ EXPECT_TRUE(scene.anchored.bottom);
+
+ // mouse is off center on the left horizontal
+ EXPECT_EQ(kClientSize.width() / (scale * 2), scene.mousePosition.x());
+ // mouse is centered vertical
+ EXPECT_EQ(kSmallInversed.height() / 2, scene.mousePosition.y());
+}
+
+TEST_F(SceneViewTest, ContainsTouchPoint) {
+ int midWidth = kClientWidth / 2;
+ int midHeight = kClientHeight / 2;
+ // left
+ EXPECT_FALSE([scene_ containsTouchPoint:CGPointMake(-1, midHeight)]);
+ EXPECT_TRUE([scene_ containsTouchPoint:CGPointMake(0, midHeight)]);
+ // right
+ EXPECT_FALSE(
+ [scene_ containsTouchPoint:CGPointMake(kClientWidth, midHeight)]);
+ EXPECT_TRUE(
+ [scene_ containsTouchPoint:CGPointMake(kClientWidth - 1, midHeight)]);
+ // top
+ EXPECT_FALSE(
+ [scene_ containsTouchPoint:CGPointMake(midWidth, kClientHeight)]);
+ EXPECT_TRUE(
+ [scene_ containsTouchPoint:CGPointMake(midWidth, kClientHeight - 1)]);
+ // bottom
+ EXPECT_FALSE([scene_ containsTouchPoint:CGPointMake(midWidth, -1)]);
+ EXPECT_TRUE([scene_ containsTouchPoint:CGPointMake(midWidth, 0)]);
+
+ [scene_ setMarginsFromLeft:10 right:10 top:10 bottom:10];
+
+ // left
+ EXPECT_FALSE([scene_ containsTouchPoint:CGPointMake(9, midHeight)]);
+ EXPECT_TRUE([scene_ containsTouchPoint:CGPointMake(10, midHeight)]);
+ // right
+ EXPECT_FALSE(
+ [scene_ containsTouchPoint:CGPointMake(kClientWidth - 10, midHeight)]);
+ EXPECT_TRUE(
+ [scene_ containsTouchPoint:CGPointMake(kClientWidth - 11, midHeight)]);
+ // top
+ EXPECT_FALSE(
+ [scene_ containsTouchPoint:CGPointMake(midWidth, kClientHeight - 10)]);
+ EXPECT_TRUE(
+ [scene_ containsTouchPoint:CGPointMake(midWidth, kClientHeight - 11)]);
+ // bottom
+ EXPECT_FALSE([scene_ containsTouchPoint:CGPointMake(midWidth, 9)]);
+ EXPECT_TRUE([scene_ containsTouchPoint:CGPointMake(midWidth, 10)]);
+}
+
+TEST_F(SceneViewTest,
+ UpdateMousePositionAndAnchorsWithTranslationNoMovement) {
+
+ webrtc::DesktopVector originalPosition = scene_.mousePosition;
+ AnchorPosition originalAnchors = scene_.anchored;
+
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(0, 0)
+ scale:1];
+
+ webrtc::DesktopVector newPosition = scene_.mousePosition;
+
+ EXPECT_EQ(0, abs(originalPosition.x() - newPosition.x()));
+ EXPECT_EQ(0, abs(originalPosition.y() - newPosition.y()));
+
+ EXPECT_EQ(originalAnchors.right, scene_.anchored.right);
+ EXPECT_EQ(originalAnchors.top, scene_.anchored.top);
+ EXPECT_EQ(originalAnchors.left, scene_.anchored.left);
+ EXPECT_EQ(originalAnchors.bottom, scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.tickPanVelocity);
+}
+
+TEST_F(SceneViewTest,
+ UpdateMousePositionAndAnchorsWithTranslationTowardLeftAndTop) {
+ // Translation is in a coordinate space where (0,0) is the bottom left of the
+ // view. Mouse position in in a coordinate space where (0,0) is the top left
+ // of the view. So |y| is moved in the negative direction.
+
+ webrtc::DesktopVector originalPosition = scene_.mousePosition;
+
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(2, -1)
+ scale:1];
+
+ webrtc::DesktopVector newPosition = scene_.mousePosition;
+
+ // We could do these checks as a single test, for a positive vs negative
+ // difference. But this style has a clearer meaning that the position moved
+ // toward or away from the origin.
+ EXPECT_LT(newPosition.x(), originalPosition.x());
+ EXPECT_LT(newPosition.y(), originalPosition.y());
+ EXPECT_EQ(2, abs(originalPosition.x() - newPosition.x()));
+ EXPECT_EQ(1, abs(originalPosition.y() - newPosition.y()));
+
+ EXPECT_TRUE(scene_.anchored.left);
+ EXPECT_TRUE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.bottom);
+
+ EXPECT_TRUE(scene_.tickPanVelocity);
+
+ // move much further than the bounds allow
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_
+ updateMousePositionAndAnchorsWithTranslation:CGPointMake(10000, -10000)
+ scale:1];
+
+ newPosition = scene_.mousePosition;
+
+ EXPECT_EQ(0, newPosition.x());
+ EXPECT_EQ(0, newPosition.y());
+
+ EXPECT_TRUE(scene_.anchored.left);
+ EXPECT_TRUE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.tickPanVelocity);
+}
+
+TEST_F(SceneViewTest,
+ UpdateMousePositionAndAnchorsWithTranslationTowardLeftAndBottom) {
+ webrtc::DesktopVector originalPosition = scene_.mousePosition;
+
+ // see notes for Test
+ // UpdateMousePositionAndAnchorsWithTranslationTowardLeftAndTop
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(2, 1)
+ scale:1];
+ webrtc::DesktopVector newPosition = scene_.mousePosition;
+
+ EXPECT_LT(newPosition.x(), originalPosition.x());
+ EXPECT_GT(newPosition.y(), originalPosition.y());
+ EXPECT_EQ(2, abs(originalPosition.x() - newPosition.x()));
+ EXPECT_EQ(1, abs(originalPosition.y() - newPosition.y()));
+
+ EXPECT_TRUE(scene_.anchored.left);
+ EXPECT_TRUE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.top);
+
+ EXPECT_TRUE(scene_.tickPanVelocity);
+
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(10000, 10000)
+ scale:1];
+ newPosition = scene_.mousePosition;
+
+ EXPECT_EQ(0, newPosition.x());
+ EXPECT_EQ(scene_.frameSize.height() - 1, newPosition.y());
+
+ EXPECT_TRUE(scene_.anchored.left);
+ EXPECT_TRUE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.tickPanVelocity);
+}
+
+TEST_F(SceneViewTest,
+ UpdateMousePositionAndAnchorsWithTranslationTowardRightAndTop) {
+ webrtc::DesktopVector originalPosition = scene_.mousePosition;
+
+ // see notes for Test
+ // UpdateMousePositionAndAnchorsWithTranslationTowardLeftAndTop
+
+ // When moving to the right the mouse remains centered since the horizontal
+ // display space is larger than the view space
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(-2, -1)
+ scale:1];
+ webrtc::DesktopVector newPosition = scene_.mousePosition;
+
+ EXPECT_LT(newPosition.y(), originalPosition.y());
+ EXPECT_EQ(0, abs(originalPosition.x() - newPosition.x()));
+ EXPECT_EQ(1, abs(originalPosition.y() - newPosition.y()));
+
+ EXPECT_TRUE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.anchored.left);
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.bottom);
+
+ EXPECT_TRUE(scene_.tickPanVelocity);
+
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_
+ updateMousePositionAndAnchorsWithTranslation:CGPointMake(-10000, -10000)
+ scale:1];
+ newPosition = scene_.mousePosition;
+
+ EXPECT_EQ(scene_.frameSize.width() - 1, newPosition.x());
+ EXPECT_EQ(0, newPosition.y());
+
+ EXPECT_TRUE(scene_.anchored.right);
+ EXPECT_TRUE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.anchored.left);
+ EXPECT_FALSE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.tickPanVelocity);
+}
+
+TEST_F(SceneViewTest,
+ UpdateMousePositionAndAnchorsWithTranslationTowardRightAndBottom) {
+ webrtc::DesktopVector originalPosition = scene_.mousePosition;
+
+ // see notes for Test
+ // UpdateMousePositionAndAnchorsWithTranslationTowardLeftAndTop
+
+ // When moving to the right the mouse remains centered since the horizontal
+ // display space is larger than the view space
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_ updateMousePositionAndAnchorsWithTranslation:CGPointMake(-2, 1)
+ scale:1];
+ webrtc::DesktopVector newPosition = scene_.mousePosition;
+
+ EXPECT_GT(newPosition.y(), originalPosition.y());
+ EXPECT_EQ(0, abs(originalPosition.x() - newPosition.x()));
+ EXPECT_EQ(1, abs(originalPosition.y() - newPosition.y()));
+
+ EXPECT_TRUE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.anchored.left);
+ EXPECT_FALSE(scene_.anchored.right);
+ EXPECT_FALSE(scene_.anchored.top);
+
+ EXPECT_TRUE(scene_.tickPanVelocity);
+
+ [scene_ setPanVelocity:CGPointMake(1, 1)];
+ [scene_
+ updateMousePositionAndAnchorsWithTranslation:CGPointMake(-10000, 10000)
+ scale:1];
+ newPosition = scene_.mousePosition;
+
+ EXPECT_EQ(scene_.frameSize.width() - 1, newPosition.x());
+ EXPECT_EQ(scene_.frameSize.height() - 1, newPosition.y());
+
+ EXPECT_TRUE(scene_.anchored.right);
+ EXPECT_TRUE(scene_.anchored.bottom);
+
+ EXPECT_FALSE(scene_.anchored.left);
+ EXPECT_FALSE(scene_.anchored.top);
+
+ EXPECT_FALSE(scene_.tickPanVelocity);
+}
+
+TEST(SceneViewTest_Static, PositionDeltaFromScaling) {
+
+ // Legend:
+ // * anchored point or end point
+ // | unanchored endpoint
+ // - onscreen
+ // # offscreen
+
+ // *---|
+ // *-------|
+ EXPECT_EQ(
+ 0,
+ [SceneView positionDeltaFromScaling:2.0F position:0 length:100 anchor:0]);
+ // *---|
+ // *-|
+ EXPECT_EQ(
+ 0,
+ [SceneView positionDeltaFromScaling:0.5F position:0 length:100 anchor:0]);
+ // |---*
+ // |-------*
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:0
+ length:100
+ anchor:100]);
+ // |----*
+ // |--*
+ EXPECT_EQ(-50,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:0
+ length:100
+ anchor:100]);
+ // |*---|
+ // |-*-------|
+ EXPECT_EQ(25,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:0
+ length:100
+ anchor:25]);
+ // |-*--|
+ // |*-|
+ EXPECT_EQ(-12.5,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:0
+ length:100
+ anchor:25]);
+ // |---*|
+ // |------*-|
+ EXPECT_EQ(75,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:0
+ length:100
+ anchor:75]);
+ // |--*-|
+ // |-*|
+ EXPECT_EQ(-37.5,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:0
+ length:100
+ anchor:75]);
+ // |-*-|
+ // |---*---|
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:0
+ length:100
+ anchor:50]);
+ // |--*--|
+ // |*|
+ EXPECT_EQ(-25,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:0
+ length:100
+ anchor:50]);
+ //////////////////////////////////
+ // Change position to 50, anchor is relatively the same
+ //////////////////////////////////
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:50
+ length:100
+ anchor:50]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:50
+ length:100
+ anchor:50]);
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:50
+ length:100
+ anchor:150]);
+ EXPECT_EQ(-50,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:50
+ length:100
+ anchor:150]);
+ EXPECT_EQ(25,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:50
+ length:100
+ anchor:75]);
+ EXPECT_EQ(-12.5,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:50
+ length:100
+ anchor:75]);
+ EXPECT_EQ(75,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:50
+ length:100
+ anchor:125]);
+ EXPECT_EQ(-37.5,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:50
+ length:100
+ anchor:125]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:50
+ length:100
+ anchor:100]);
+ EXPECT_EQ(-25,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:50
+ length:100
+ anchor:100]);
+
+ //////////////////////////////////
+ // Change position to -50, length to 200, anchor is relatively the same
+ //////////////////////////////////
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:-50
+ length:200
+ anchor:-50]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:-50
+ length:200
+ anchor:-50]);
+ EXPECT_EQ(200,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:-50
+ length:200
+ anchor:150]);
+ EXPECT_EQ(-100,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:-50
+ length:200
+ anchor:150]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:-50
+ length:200
+ anchor:0]);
+ EXPECT_EQ(-25,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:-50
+ length:200
+ anchor:0]);
+ EXPECT_EQ(150,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:-50
+ length:200
+ anchor:100]);
+ EXPECT_EQ(-75,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:-50
+ length:200
+ anchor:100]);
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromScaling:2.0F
+ position:-50
+ length:200
+ anchor:50]);
+ EXPECT_EQ(-50,
+ [SceneView positionDeltaFromScaling:0.5F
+ position:-50
+ length:200
+ anchor:50]);
+}
+
+TEST(SceneViewTest_Static, PositionDeltaFromTranslation) {
+ // Anchored on both sides. Center it by using 1/2 the free space, offset by
+ // the current position
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromTranslation:100
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(-50,
+ [SceneView positionDeltaFromTranslation:0
+ position:100
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:100
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:200
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+
+ // Anchored only on the left. Don't move it
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:100
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:0
+ position:100
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:200
+ scaleingPositionDelta:100
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:200
+ scaleingPositionDelta:0
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ // Anchored only on the right. Move by the scaling delta
+ EXPECT_EQ(25,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:25
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromTranslation:100
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:50
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(75,
+ [SceneView positionDeltaFromTranslation:0
+ position:100
+ freeSpace:100
+ scaleingPositionDelta:75
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:100
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(125,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:200
+ scaleingPositionDelta:125
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ // Not anchored, translate and move by the scaling delta
+ EXPECT_EQ(0,
+ [SceneView positionDeltaFromTranslation:0
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(25,
+ [SceneView positionDeltaFromTranslation:25
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(50,
+ [SceneView positionDeltaFromTranslation:50
+ position:100
+ freeSpace:100
+ scaleingPositionDelta:0
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(175,
+ [SceneView positionDeltaFromTranslation:75
+ position:0
+ freeSpace:100
+ scaleingPositionDelta:100
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(100,
+ [SceneView positionDeltaFromTranslation:100
+ position:0
+ freeSpace:200
+ scaleingPositionDelta:0
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+}
+
+TEST(SceneViewTest_Static, BoundDeltaFromPosition) {
+ // Entire entity fits in our view, lower bound is not less than the
+ // upperBound. The delta is bounded to the lowerBound.
+ EXPECT_EQ(200,
+ [SceneView boundDeltaFromPosition:0
+ delta:0
+ lowerBound:200
+ upperBound:100]);
+ EXPECT_EQ(100,
+ [SceneView boundDeltaFromPosition:100
+ delta:0
+ lowerBound:200
+ upperBound:100]);
+ EXPECT_EQ(200,
+ [SceneView boundDeltaFromPosition:0
+ delta:100
+ lowerBound:200
+ upperBound:100]);
+ EXPECT_EQ(150,
+ [SceneView boundDeltaFromPosition:50
+ delta:100
+ lowerBound:200
+ upperBound:200]);
+ // Entity does not fit in our view. The result would be out of bounds on the
+ // high bound. The delta is bounded to the upper bound and the delta from the
+ // position is returned.
+ EXPECT_EQ(100,
+ [SceneView boundDeltaFromPosition:0
+ delta:1000
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(99,
+ [SceneView boundDeltaFromPosition:1
+ delta:1000
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(-50,
+ [SceneView boundDeltaFromPosition:150
+ delta:1000
+ lowerBound:50
+ upperBound:100]);
+ EXPECT_EQ(100,
+ [SceneView boundDeltaFromPosition:100
+ delta:1000
+ lowerBound:0
+ upperBound:200]);
+ // Entity does not fit in our view. The result would be out of bounds on the
+ // low bound. The delta is bounded to the lower bound and the delta from the
+ // position is returned.
+ EXPECT_EQ(0,
+ [SceneView boundDeltaFromPosition:0
+ delta:-1000
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(-20,
+ [SceneView boundDeltaFromPosition:20
+ delta:-1000
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(21,
+ [SceneView boundDeltaFromPosition:29
+ delta:-1000
+ lowerBound:50
+ upperBound:100]);
+ EXPECT_EQ(1,
+ [SceneView boundDeltaFromPosition:-1
+ delta:-1000
+ lowerBound:0
+ upperBound:200]);
+ // Entity does not fit in our view. The result is in bounds. The delta is
+ // returned unchanged.
+ EXPECT_EQ(50,
+ [SceneView boundDeltaFromPosition:0
+ delta:50
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(-10,
+ [SceneView boundDeltaFromPosition:20
+ delta:-10
+ lowerBound:0
+ upperBound:100]);
+ EXPECT_EQ(31,
+ [SceneView boundDeltaFromPosition:29
+ delta:31
+ lowerBound:50
+ upperBound:100]);
+ EXPECT_EQ(50,
+ [SceneView boundDeltaFromPosition:100
+ delta:50
+ lowerBound:0
+ upperBound:200]);
+}
+
+TEST(SceneViewTest_Static, BoundMouseGivenNextPosition) {
+ // Mouse would move off screen in the negative
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:-1
+ maxPosition:50
+ centerPosition:2
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:-1
+ maxPosition:25
+ centerPosition:99
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:-11
+ maxPosition:0
+ centerPosition:-52
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:-11
+ maxPosition:-100
+ centerPosition:44
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:-1
+ maxPosition:50
+ centerPosition:-20
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+
+ // Mouse would move off screen in the positive
+ EXPECT_EQ(49,
+ [SceneView boundMouseGivenNextPosition:50
+ maxPosition:50
+ centerPosition:2
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(24,
+ [SceneView boundMouseGivenNextPosition:26
+ maxPosition:25
+ centerPosition:99
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(-1,
+ [SceneView boundMouseGivenNextPosition:1
+ maxPosition:0
+ centerPosition:-52
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(-101,
+ [SceneView boundMouseGivenNextPosition:0
+ maxPosition:-100
+ centerPosition:44
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(49,
+ [SceneView boundMouseGivenNextPosition:60
+ maxPosition:50
+ centerPosition:-20
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+
+ // Mouse is not out of bounds, and not anchored. The Center is returned.
+ EXPECT_EQ(2,
+ [SceneView boundMouseGivenNextPosition:0
+ maxPosition:100
+ centerPosition:2
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(99,
+ [SceneView boundMouseGivenNextPosition:25
+ maxPosition:100
+ centerPosition:99
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(-52,
+ [SceneView boundMouseGivenNextPosition:99
+ maxPosition:100
+ centerPosition:-52
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(44,
+ [SceneView boundMouseGivenNextPosition:120
+ maxPosition:200
+ centerPosition:44
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(-20,
+ [SceneView boundMouseGivenNextPosition:180
+ maxPosition:200
+ centerPosition:-20
+ isAnchoredLow:NO
+ isAnchoredHigh:NO]);
+
+ // Mouse is not out of bounds, and anchored. The position closest
+ // to the anchor is returned.
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:0
+ maxPosition:100
+ centerPosition:2
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(25,
+ [SceneView boundMouseGivenNextPosition:25
+ maxPosition:100
+ centerPosition:99
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(-52,
+ [SceneView boundMouseGivenNextPosition:99
+ maxPosition:100
+ centerPosition:-52
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(44,
+ [SceneView boundMouseGivenNextPosition:120
+ maxPosition:200
+ centerPosition:44
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(-20,
+ [SceneView boundMouseGivenNextPosition:180
+ maxPosition:200
+ centerPosition:-20
+ isAnchoredLow:YES
+ isAnchoredHigh:NO]);
+ EXPECT_EQ(2,
+ [SceneView boundMouseGivenNextPosition:0
+ maxPosition:100
+ centerPosition:2
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(99,
+ [SceneView boundMouseGivenNextPosition:25
+ maxPosition:100
+ centerPosition:99
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(99,
+ [SceneView boundMouseGivenNextPosition:99
+ maxPosition:100
+ centerPosition:-52
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(120,
+ [SceneView boundMouseGivenNextPosition:120
+ maxPosition:200
+ centerPosition:44
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(180,
+ [SceneView boundMouseGivenNextPosition:180
+ maxPosition:200
+ centerPosition:-20
+ isAnchoredLow:NO
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(0,
+ [SceneView boundMouseGivenNextPosition:0
+ maxPosition:100
+ centerPosition:2
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(25,
+ [SceneView boundMouseGivenNextPosition:25
+ maxPosition:100
+ centerPosition:99
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(99,
+ [SceneView boundMouseGivenNextPosition:99
+ maxPosition:100
+ centerPosition:-52
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(120,
+ [SceneView boundMouseGivenNextPosition:120
+ maxPosition:200
+ centerPosition:44
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+ EXPECT_EQ(180,
+ [SceneView boundMouseGivenNextPosition:180
+ maxPosition:200
+ centerPosition:-20
+ isAnchoredLow:YES
+ isAnchoredHigh:YES]);
+}
+
+TEST(SceneViewTest_Static, BoundVelocity) {
+ // Outside bounds of the axis
+ EXPECT_EQ(0, [SceneView boundVelocity:5.0f axisLength:100 mousePosition:0]);
+ EXPECT_EQ(0, [SceneView boundVelocity:5.0f axisLength:100 mousePosition:99]);
+ EXPECT_EQ(0, [SceneView boundVelocity:5.0f axisLength:200 mousePosition:200]);
+ // Not outside bounds of the axis
+ EXPECT_EQ(5.0f,
+ [SceneView boundVelocity:5.0f axisLength:100 mousePosition:1]);
+ EXPECT_EQ(5.0f,
+ [SceneView boundVelocity:5.0f axisLength:100 mousePosition:98]);
+ EXPECT_EQ(5.0f,
+ [SceneView boundVelocity:5.0f axisLength:200 mousePosition:100]);
+}
+
+TEST_F(SceneViewTest, TickPanVelocity) {
+ // We are in the large frame, which can pan left and right but not up and
+ // down. Start by resizing it to allow panning up and down.
+
+ [scene_ panAndZoom:CGPointMake(0, 0) scaleBy:2.0f];
+
+ // Going up and right
+ [scene_ setPanVelocity:CGPointMake(1000, 1000)];
+ [scene_ tickPanVelocity];
+
+ webrtc::DesktopVector pos = scene_.mousePosition;
+ int loopLimit = 0;
+ bool didMove = false;
+ bool inMotion = true;
+
+ while (inMotion && loopLimit < 100) {
+ inMotion = [scene_ tickPanVelocity];
+ if (inMotion) {
+ ASSERT_TRUE(pos.x() <= scene_.mousePosition.x()) << " after " << loopLimit
+ << " iterations.";
+ ASSERT_TRUE(pos.y() <= scene_.mousePosition.y()) << " after " << loopLimit
+ << " iterations.";
+ didMove = true;
+ }
+ pos = scene_.mousePosition;
+ loopLimit++;
+ }
+
+ EXPECT_LT(1, loopLimit);
+ EXPECT_TRUE(!inMotion);
+ EXPECT_TRUE(didMove);
+
+ // Going down and left
+ [scene_ setPanVelocity:CGPointMake(-1000, -1000)];
+ [scene_ tickPanVelocity];
+
+ pos = scene_.mousePosition;
+ loopLimit = 0;
+ didMove = false;
+ inMotion = true;
+
+ while (inMotion && loopLimit < 100) {
+ inMotion = [scene_ tickPanVelocity];
+ if (inMotion) {
+ ASSERT_TRUE(pos.x() >= scene_.mousePosition.x()) << " after " << loopLimit
+ << " iterations.";
+ ASSERT_TRUE(pos.y() >= scene_.mousePosition.y()) << " after " << loopLimit
+ << " iterations.";
+ didMove = true;
+ }
+ pos = scene_.mousePosition;
+ loopLimit++;
+ }
+
+ EXPECT_LT(1, loopLimit);
+ EXPECT_TRUE(!inMotion);
+ EXPECT_TRUE(didMove);
+}
+
+} // namespace remoting \ No newline at end of file
diff --git a/remoting/ios/utility.h b/remoting/ios/utility.h
new file mode 100644
index 0000000000..ac6a2a43e2
--- /dev/null
+++ b/remoting/ios/utility.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_IOS_UTILITY_H_
+#define REMOTING_IOS_UTILITY_H_
+
+#import <Foundation/Foundation.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
+#include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
+
+#import "remoting/ios/bridge/host_proxy.h"
+
+typedef struct {
+ scoped_ptr<webrtc::BasicDesktopFrame> image;
+ scoped_ptr<webrtc::DesktopVector> offset;
+} GLRegion;
+
+@interface Utility : NSObject
+
++ (BOOL)isPad;
+
++ (BOOL)isInLandscapeMode;
+
+// Return the resolution in respect to orientation
++ (CGSize)getOrientatedSize:(CGSize)size
+ shouldWidthBeLongestSide:(BOOL)shouldWidthBeLongestSide;
+
++ (void)showAlert:(NSString*)title message:(NSString*)message;
+
++ (NSString*)appVersionNumberDisplayString;
+
+// GL Binding Context requires some specific flags for the type of textures
+// being drawn
++ (void)bindTextureForIOS:(GLuint)glName;
+
+// Sometimes its necessary to read gl errors. This is called in various places
+// while working in the GL Context
++ (void)logGLErrorCode:(NSString*)funcName;
+
++ (void)drawSubRectToGLFromRectOfSize:(const webrtc::DesktopSize&)rectSize
+ subRect:(const webrtc::DesktopRect&)subRect
+ data:(const uint8_t*)data;
+
++ (void)moveMouse:(HostProxy*)controller at:(const webrtc::DesktopVector&)point;
+
++ (void)leftClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point;
+
++ (void)middleClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point;
+
++ (void)rightClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point;
+
++ (void)mouseScroll:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point
+ delta:(const webrtc::DesktopVector&)delta;
+
+@end
+
+#endif // REMOTING_IOS_UTILITY_H_
diff --git a/remoting/ios/utility.mm b/remoting/ios/utility.mm
new file mode 100644
index 0000000000..212299783b
--- /dev/null
+++ b/remoting/ios/utility.mm
@@ -0,0 +1,150 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "Utility.h"
+
+@implementation Utility
+
++ (BOOL)isPad {
+ return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad);
+}
+
++ (BOOL)isInLandscapeMode {
+ UIInterfaceOrientation orientation =
+ [UIApplication sharedApplication].statusBarOrientation;
+
+ if ((orientation == UIInterfaceOrientationLandscapeLeft) ||
+ (orientation == UIInterfaceOrientationLandscapeRight)) {
+ return YES;
+ }
+ return NO;
+}
+
++ (CGSize)getOrientatedSize:(CGSize)size
+ shouldWidthBeLongestSide:(BOOL)shouldWidthBeLongestSide {
+ if (shouldWidthBeLongestSide && (size.height > size.width)) {
+ return CGSizeMake(size.height, size.width);
+ }
+ return size;
+}
+
++ (void)showAlert:(NSString*)title message:(NSString*)message {
+ UIAlertView* alert;
+ alert = [[UIAlertView alloc] init];
+ alert.title = title;
+ alert.message = message;
+ alert.delegate = nil;
+ [alert addButtonWithTitle:@"OK"];
+ [alert show];
+}
+
++ (NSString*)appVersionNumberDisplayString {
+ NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
+
+ NSString* majorVersion =
+ [infoDictionary objectForKey:@"CFBundleShortVersionString"];
+ NSString* minorVersion = [infoDictionary objectForKey:@"CFBundleVersion"];
+
+ return [NSString
+ stringWithFormat:@"Version %@ (%@)", majorVersion, minorVersion];
+}
+
++ (void)bindTextureForIOS:(GLuint)glName {
+ glBindTexture(GL_TEXTURE_2D, glName);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
++ (void)logGLErrorCode:(NSString*)funcName {
+ GLenum errorCode = 1;
+
+ while (errorCode != 0) {
+ errorCode = glGetError(); // I don't know why this is returning an error
+ // on the first call to this function, but if I
+ // don't read it, then stuff doesn't work...
+#if DEBUG
+ if (errorCode != 0) {
+ NSLog(@"glerror in %@: %X", funcName, errorCode);
+ }
+#endif // DEBUG
+ }
+}
+
++ (void)drawSubRectToGLFromRectOfSize:(const webrtc::DesktopSize&)rectSize
+ subRect:(const webrtc::DesktopRect&)subRect
+ data:(const uint8_t*)data {
+ DCHECK(rectSize.width() >= subRect.width());
+ DCHECK(rectSize.height() >= subRect.height());
+ DCHECK(rectSize.width() >= (subRect.left() + subRect.width()));
+ DCHECK(rectSize.height() >= (subRect.top() + subRect.height()));
+ DCHECK(data);
+
+ glTexSubImage2D(GL_TEXTURE_2D,
+ 0,
+ subRect.left(),
+ subRect.top(),
+ subRect.width(),
+ subRect.height(),
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ data);
+}
+
++ (void)moveMouse:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point {
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:0
+ buttonDown:NO];
+}
+
++ (void)leftClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point {
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:1
+ buttonDown:YES];
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:1
+ buttonDown:NO];
+}
+
++ (void)middleClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point {
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:2
+ buttonDown:YES];
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:2
+ buttonDown:NO];
+}
+
++ (void)rightClickOn:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point {
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:3
+ buttonDown:YES];
+ [controller mouseAction:point
+ wheelDelta:webrtc::DesktopVector(0, 0)
+ whichButton:3
+ buttonDown:NO];
+}
+
++ (void)mouseScroll:(HostProxy*)controller
+ at:(const webrtc::DesktopVector&)point
+ delta:(const webrtc::DesktopVector&)delta {
+ [controller mouseAction:point wheelDelta:delta whichButton:0 buttonDown:NO];
+}
+
+@end \ No newline at end of file
diff --git a/remoting/jingle_glue/server_log_entry.cc b/remoting/jingle_glue/server_log_entry.cc
new file mode 100644
index 0000000000..e90721b14a
--- /dev/null
+++ b/remoting/jingle_glue/server_log_entry.cc
@@ -0,0 +1,88 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/server_log_entry.h"
+
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "remoting/base/constants.h"
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+
+using base::SysInfo;
+using buzz::QName;
+using buzz::XmlElement;
+
+namespace remoting {
+
+namespace {
+
+const char kLogCommand[] = "log";
+const char kLogEntry[] = "entry";
+
+const char kKeyEventName[] = "event-name";
+
+const char kKeyRole[] = "role";
+
+const char kKeyMode[] = "mode";
+const char kValueModeIt2Me[] = "it2me";
+const char kValueModeMe2Me[] = "me2me";
+
+const char kKeyCpu[] = "cpu";
+
+} // namespace
+
+ServerLogEntry::ServerLogEntry() {
+}
+
+ServerLogEntry::~ServerLogEntry() {
+}
+
+void ServerLogEntry::Set(const std::string& key, const std::string& value) {
+ values_map_[key] = value;
+}
+
+void ServerLogEntry::AddCpuField() {
+ Set(kKeyCpu, SysInfo::OperatingSystemArchitecture());
+}
+
+void ServerLogEntry::AddModeField(ServerLogEntry::Mode mode) {
+ const char* mode_value = NULL;
+ switch (mode) {
+ case IT2ME:
+ mode_value = kValueModeIt2Me;
+ break;
+ case ME2ME:
+ mode_value = kValueModeMe2Me;
+ break;
+ default:
+ NOTREACHED();
+ }
+ Set(kKeyMode, mode_value);
+}
+
+void ServerLogEntry::AddRoleField(const char* role) {
+ Set(kKeyRole, role);
+}
+
+void ServerLogEntry::AddEventNameField(const char* name) {
+ Set(kKeyEventName, name);
+}
+
+// static
+scoped_ptr<XmlElement> ServerLogEntry::MakeStanza() {
+ return scoped_ptr<XmlElement>(
+ new XmlElement(QName(kChromotingXmlNamespace, kLogCommand)));
+}
+
+scoped_ptr<XmlElement> ServerLogEntry::ToStanza() const {
+ scoped_ptr<XmlElement> stanza(new XmlElement(QName(
+ kChromotingXmlNamespace, kLogEntry)));
+ ValuesMap::const_iterator iter;
+ for (iter = values_map_.begin(); iter != values_map_.end(); ++iter) {
+ stanza->AddAttr(QName(std::string(), iter->first), iter->second);
+ }
+ return stanza.Pass();
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/server_log_entry.h b/remoting/jingle_glue/server_log_entry.h
new file mode 100644
index 0000000000..ab2314df6f
--- /dev/null
+++ b/remoting/jingle_glue/server_log_entry.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_SERVER_LOG_ENTRY_H_
+#define REMOTING_JINGLE_GLUE_SERVER_LOG_ENTRY_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+
+namespace buzz {
+class XmlElement;
+} // namespace buzz
+
+namespace remoting {
+
+// Utility class for building log entries to send to the remoting bot. This is
+// a wrapper around a key/value map and is copyable so it can be used in STL
+// containers.
+class ServerLogEntry {
+ public:
+ // The mode of a connection.
+ enum Mode {
+ IT2ME,
+ ME2ME
+ };
+
+ ServerLogEntry();
+ ~ServerLogEntry();
+
+ // Sets an arbitrary key/value entry.
+ void Set(const std::string& key, const std::string& value);
+
+ // Adds a field describing the CPU type of the platform.
+ void AddCpuField();
+
+ // Adds a field describing the mode of a connection to this log entry.
+ void AddModeField(Mode mode);
+
+ // Adds a field describing the role (client/host).
+ void AddRoleField(const char* role);
+
+ // Adds a field describing the type of log entry.
+ void AddEventNameField(const char* name);
+
+ // Constructs a log stanza. The caller should add one or more log entry
+ // stanzas as children of this stanza, before sending the log stanza to
+ // the remoting bot.
+ static scoped_ptr<buzz::XmlElement> MakeStanza();
+
+ // Converts this object to an XML stanza.
+ scoped_ptr<buzz::XmlElement> ToStanza() const;
+
+ private:
+ typedef std::map<std::string, std::string> ValuesMap;
+
+ ValuesMap values_map_;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_SERVER_LOG_ENTRY_H_
diff --git a/remoting/jingle_glue/server_log_entry_unittest.cc b/remoting/jingle_glue/server_log_entry_unittest.cc
new file mode 100644
index 0000000000..b92feb2053
--- /dev/null
+++ b/remoting/jingle_glue/server_log_entry_unittest.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/server_log_entry_unittest.h"
+
+#include <sstream>
+
+#include "third_party/libjingle/source/talk/xmllite/xmlelement.h"
+
+using buzz::XmlAttr;
+using buzz::XmlElement;
+
+namespace remoting {
+
+bool VerifyStanza(
+ const std::map<std::string, std::string>& key_value_pairs,
+ const std::set<std::string> keys,
+ const XmlElement* elem,
+ std::string* error) {
+ int attrCount = 0;
+ for (const XmlAttr* attr = elem->FirstAttr(); attr != NULL;
+ attr = attr->NextAttr(), attrCount++) {
+ if (attr->Name().Namespace().length() != 0) {
+ *error = "attribute has non-empty namespace " +
+ attr->Name().Namespace();
+ return false;
+ }
+ const std::string& key = attr->Name().LocalPart();
+ const std::string& value = attr->Value();
+ std::map<std::string, std::string>::const_iterator iter =
+ key_value_pairs.find(key);
+ if (iter == key_value_pairs.end()) {
+ if (keys.find(key) == keys.end()) {
+ *error = "unexpected attribute " + key;
+ return false;
+ }
+ } else {
+ if (iter->second != value) {
+ *error = "attribute " + key + " has value " + iter->second +
+ ": expected " + value;
+ return false;
+ }
+ }
+ }
+ int attr_count_expected = key_value_pairs.size() + keys.size();
+ if (attrCount != attr_count_expected) {
+ std::stringstream s;
+ s << "stanza has " << attrCount << " keys: expected "
+ << attr_count_expected;
+ *error = s.str();
+ return false;
+ }
+ return true;
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/server_log_entry_unittest.h b/remoting/jingle_glue/server_log_entry_unittest.h
new file mode 100644
index 0000000000..523628befa
--- /dev/null
+++ b/remoting/jingle_glue/server_log_entry_unittest.h
@@ -0,0 +1,25 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <set>
+#include <string>
+
+namespace buzz {
+class XmlElement;
+} // namespace buzz
+
+namespace remoting {
+
+// Verifies a logging stanza.
+// |keyValuePairs| lists the keys that must have specified values, and |keys|
+// lists the keys that must be present, but may have arbitrary values.
+// There must be no other keys.
+bool VerifyStanza(
+ const std::map<std::string, std::string>& key_value_pairs,
+ const std::set<std::string> keys,
+ const buzz::XmlElement* elem,
+ std::string* error);
+
+} // namespace remoting
diff --git a/remoting/proto/control.proto b/remoting/proto/control.proto
index 2d2baf620b..c24343ad6f 100644
--- a/remoting/proto/control.proto
+++ b/remoting/proto/control.proto
@@ -28,6 +28,10 @@ message ClientResolution {
message VideoControl {
// Enables the video channel if true, pauses if false.
optional bool enable = 1;
+
+ // Controls whether lossless encode and color translation are requested.
+ optional bool lossless_encode = 2;
+ optional bool lossless_color = 3;
}
message AudioControl {
diff --git a/remoting/protocol/connection_to_client.h b/remoting/protocol/connection_to_client.h
index 9a64dcdfd9..b80e9fd9fe 100644
--- a/remoting/protocol/connection_to_client.h
+++ b/remoting/protocol/connection_to_client.h
@@ -16,10 +16,6 @@
#include "remoting/protocol/session.h"
#include "remoting/protocol/video_writer.h"
-namespace net {
-class IPEndPoint;
-} // namespace net
-
namespace remoting {
namespace protocol {
diff --git a/remoting/protocol/connection_to_host.cc b/remoting/protocol/connection_to_host.cc
index d2ce7ff311..fdbd0f2c65 100644
--- a/remoting/protocol/connection_to_host.cc
+++ b/remoting/protocol/connection_to_host.cc
@@ -51,40 +51,18 @@ ConnectionToHost::~ConnectionToHost() {
signal_strategy_->RemoveListener(this);
}
-ClipboardStub* ConnectionToHost::clipboard_stub() {
- return &clipboard_forwarder_;
-}
-
-HostStub* ConnectionToHost::host_stub() {
- // TODO(wez): Add a HostFilter class, equivalent to input filter.
- return control_dispatcher_.get();
-}
-
-InputStub* ConnectionToHost::input_stub() {
- return &event_forwarder_;
-}
-
void ConnectionToHost::Connect(SignalStrategy* signal_strategy,
- const std::string& host_jid,
- const std::string& host_public_key,
scoped_ptr<TransportFactory> transport_factory,
scoped_ptr<Authenticator> authenticator,
- HostEventCallback* event_callback,
- ClientStub* client_stub,
- ClipboardStub* clipboard_stub,
- VideoStub* video_stub,
- AudioStub* audio_stub) {
+ const std::string& host_jid,
+ const std::string& host_public_key,
+ HostEventCallback* event_callback) {
+ DCHECK(client_stub_);
+ DCHECK(clipboard_stub_);
+ DCHECK(monitored_video_stub_);
+
signal_strategy_ = signal_strategy;
event_callback_ = event_callback;
- client_stub_ = client_stub;
- clipboard_stub_ = clipboard_stub;
- monitored_video_stub_.reset(new MonitoredVideoStub(
- video_stub,
- base::TimeDelta::FromSeconds(
- MonitoredVideoStub::kConnectivityCheckDelaySeconds),
- base::Bind(&ConnectionToHost::OnVideoChannelStatus,
- base::Unretained(this))));
- audio_stub_ = audio_stub;
authenticator_ = authenticator.Pass();
// Save jid of the host. The actual connection is created later after
@@ -105,6 +83,41 @@ const SessionConfig& ConnectionToHost::config() {
return session_->config();
}
+ClipboardStub* ConnectionToHost::clipboard_forwarder() {
+ return &clipboard_forwarder_;
+}
+
+HostStub* ConnectionToHost::host_stub() {
+ // TODO(wez): Add a HostFilter class, equivalent to input filter.
+ return control_dispatcher_.get();
+}
+
+InputStub* ConnectionToHost::input_stub() {
+ return &event_forwarder_;
+}
+
+void ConnectionToHost::set_client_stub(ClientStub* client_stub) {
+ client_stub_ = client_stub;
+}
+
+void ConnectionToHost::set_clipboard_stub(ClipboardStub* clipboard_stub) {
+ clipboard_stub_ = clipboard_stub;
+}
+
+void ConnectionToHost::set_video_stub(VideoStub* video_stub) {
+ DCHECK(video_stub);
+ monitored_video_stub_.reset(new MonitoredVideoStub(
+ video_stub,
+ base::TimeDelta::FromSeconds(
+ MonitoredVideoStub::kConnectivityCheckDelaySeconds),
+ base::Bind(&ConnectionToHost::OnVideoChannelStatus,
+ base::Unretained(this))));
+}
+
+void ConnectionToHost::set_audio_stub(AudioStub* audio_stub) {
+ audio_stub_ = audio_stub;
+}
+
void ConnectionToHost::OnSignalStrategyStateChange(
SignalStrategy::State state) {
DCHECK(CalledOnValidThread());
@@ -129,8 +142,10 @@ void ConnectionToHost::OnSessionManagerReady() {
// After SessionManager is initialized we can try to connect to the host.
scoped_ptr<CandidateSessionConfig> candidate_config =
CandidateSessionConfig::CreateDefault();
- if (!audio_stub_)
- CandidateSessionConfig::DisableAudioChannel(candidate_config.get());
+ if (!audio_stub_) {
+ candidate_config->DisableAudioChannel();
+ }
+ candidate_config->EnableVideoCodec(ChannelConfig::CODEC_VP9);
session_ = session_manager_->Connect(
host_jid_, authenticator_.Pass(), candidate_config.Pass());
diff --git a/remoting/protocol/connection_to_host.h b/remoting/protocol/connection_to_host.h
index e50e041f2b..d1e13134cd 100644
--- a/remoting/protocol/connection_to_host.h
+++ b/remoting/protocol/connection_to_host.h
@@ -23,10 +23,6 @@
#include "remoting/protocol/session.h"
#include "remoting/protocol/session_manager.h"
-namespace pp {
-class Instance;
-} // namespace pp
-
namespace remoting {
class XmppProxy;
@@ -87,23 +83,35 @@ class ConnectionToHost : public SignalStrategy::Listener,
ConnectionToHost(bool allow_nat_traversal);
virtual ~ConnectionToHost();
- // |signal_strategy| must outlive connection. |audio_stub| may be
- // null, in which case audio will not be requested.
+ // Set the stubs which will handle messages from the host.
+ // The caller must ensure that stubs out-live the connection.
+ // Unless otherwise specified, all stubs must be set before Connect()
+ // is called.
+ void set_client_stub(ClientStub* client_stub);
+ void set_clipboard_stub(ClipboardStub* clipboard_stub);
+ void set_video_stub(VideoStub* video_stub);
+ // If no audio stub is specified then audio will not be requested.
+ void set_audio_stub(AudioStub* audio_stub);
+
+ // Initiates a connection to the host specified by |host_jid|.
+ // |signal_strategy| is used to signal to the host, and must outlive the
+ // connection. Data channels will be negotiated over |transport_factory|.
+ // |authenticator| will be used to authenticate the session and data channels.
+ // |event_callback| will be notified of changes in the state of the connection
+ // and must outlive the ConnectionToHost.
+ // Caller must set stubs (see below) before calling Connect.
virtual void Connect(SignalStrategy* signal_strategy,
- const std::string& host_jid,
- const std::string& host_public_key,
scoped_ptr<TransportFactory> transport_factory,
scoped_ptr<Authenticator> authenticator,
- HostEventCallback* event_callback,
- ClientStub* client_stub,
- ClipboardStub* clipboard_stub,
- VideoStub* video_stub,
- AudioStub* audio_stub);
+ const std::string& host_jid,
+ const std::string& host_public_key,
+ HostEventCallback* event_callback);
+ // Returns the session configuration that was negotiated with the host.
virtual const SessionConfig& config();
// Stubs for sending data to the host.
- virtual ClipboardStub* clipboard_stub();
+ virtual ClipboardStub* clipboard_forwarder();
virtual HostStub* host_stub();
virtual InputStub* input_stub();
diff --git a/remoting/protocol/content_description.cc b/remoting/protocol/content_description.cc
index 7a2db7d415..6935115fa1 100644
--- a/remoting/protocol/content_description.cc
+++ b/remoting/protocol/content_description.cc
@@ -145,7 +145,7 @@ XmlElement* ContentDescription::ToXml() const {
XmlElement* root = new XmlElement(
QName(kChromotingXmlNamespace, kDescriptionTag), true);
- std::vector<ChannelConfig>::const_iterator it;
+ std::list<ChannelConfig>::const_iterator it;
for (it = config()->control_configs().begin();
it != config()->control_configs().end(); ++it) {
@@ -191,7 +191,7 @@ bool ContentDescription::ParseChannelConfigs(
const char tag_name[],
bool codec_required,
bool optional,
- std::vector<ChannelConfig>* const configs) {
+ std::list<ChannelConfig>* const configs) {
QName tag(kChromotingXmlNamespace, tag_name);
const XmlElement* child = element->FirstNamed(tag);
diff --git a/remoting/protocol/content_description.h b/remoting/protocol/content_description.h
index d345507d67..65115c233f 100644
--- a/remoting/protocol/content_description.h
+++ b/remoting/protocol/content_description.h
@@ -55,7 +55,7 @@ class ContentDescription : public cricket::ContentDescription {
const char tag_name[],
bool codec_required,
bool optional,
- std::vector<ChannelConfig>* const configs);
+ std::list<ChannelConfig>* const configs);
};
} // namespace protocol
diff --git a/remoting/protocol/content_description_unittest.cc b/remoting/protocol/content_description_unittest.cc
index 25aa8b6eda..4dfc0eed87 100644
--- a/remoting/protocol/content_description_unittest.cc
+++ b/remoting/protocol/content_description_unittest.cc
@@ -51,7 +51,7 @@ TEST(ContentDescriptionTest, ParseUnknown) {
ContentDescription::ParseXml(xml.get()));
ASSERT_TRUE(parsed.get());
EXPECT_EQ(1U, parsed->config()->event_configs().size());
- EXPECT_TRUE(parsed->config()->event_configs()[0] ==
+ EXPECT_TRUE(parsed->config()->event_configs().front() ==
ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
kDefaultStreamVersion,
ChannelConfig::CODEC_UNDEFINED));
@@ -74,7 +74,7 @@ TEST(ContentDescriptionTest, NoneTransport) {
ContentDescription::ParseXml(xml.get()));
ASSERT_TRUE(parsed.get());
EXPECT_EQ(1U, parsed->config()->audio_configs().size());
- EXPECT_TRUE(parsed->config()->audio_configs()[0] == ChannelConfig());
+ EXPECT_TRUE(parsed->config()->audio_configs().front() == ChannelConfig());
}
// Verify that we can parse configs with none transport with version and
@@ -94,7 +94,7 @@ TEST(ContentDescriptionTest, NoneTransportWithCodec) {
ContentDescription::ParseXml(xml.get()));
ASSERT_TRUE(parsed.get());
EXPECT_EQ(1U, parsed->config()->audio_configs().size());
- EXPECT_TRUE(parsed->config()->audio_configs()[0] == ChannelConfig());
+ EXPECT_TRUE(parsed->config()->audio_configs().front() == ChannelConfig());
}
} // namespace protocol
diff --git a/remoting/protocol/jingle_session_unittest.cc b/remoting/protocol/jingle_session_unittest.cc
index 2f9510070e..037cbcb974 100644
--- a/remoting/protocol/jingle_session_unittest.cc
+++ b/remoting/protocol/jingle_session_unittest.cc
@@ -9,6 +9,7 @@
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
+#include "jingle/glue/thread_wrapper.h"
#include "net/socket/socket.h"
#include "net/socket/stream_socket.h"
#include "net/url_request/url_request_context_getter.h"
@@ -94,6 +95,7 @@ class JingleSessionTest : public testing::Test {
public:
JingleSessionTest() {
message_loop_.reset(new base::MessageLoopForIO());
+ jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
}
// Helper method that handles OnIncomingSession().
diff --git a/remoting/protocol/libjingle_transport_factory.cc b/remoting/protocol/libjingle_transport_factory.cc
index fd5ba0dd09..394b78ef07 100644
--- a/remoting/protocol/libjingle_transport_factory.cc
+++ b/remoting/protocol/libjingle_transport_factory.cc
@@ -4,15 +4,12 @@
#include "remoting/protocol/libjingle_transport_factory.h"
-#include "base/base64.h"
#include "base/callback.h"
-#include "base/rand_util.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/timer/timer.h"
#include "jingle/glue/channel_socket_adapter.h"
#include "jingle/glue/pseudotcp_adapter.h"
-#include "jingle/glue/thread_wrapper.h"
#include "jingle/glue/utils.h"
#include "net/base/net_errors.h"
#include "remoting/base/constants.h"
@@ -46,20 +43,6 @@ const int kReconnectDelaySeconds = 15;
// Get fresh STUN/Relay configuration every hour.
const int kJingleInfoUpdatePeriodSeconds = 3600;
-// TODO(sergeyu): Remove this function and use talk_base::CreateRandomString()
-// when it's fixed to work reliably. See crbug.com/364689 .
-std::string CreateRandomString(int length) {
- // Number of random bytes to generate base64 string at least |length|
- // characters long.
- int raw_length = (length + 1) * 3 / 4;
- std::string base64;
- base::Base64Encode(base::RandBytesAsString(raw_length), &base64);
- DCHECK(static_cast<int>(base64.size()) == length ||
- static_cast<int>(base64.size()) == length + 1);
- base64.resize(length);
- return base64;
-}
-
class LibjingleStreamTransport
: public StreamTransport,
public base::SupportsWeakPtr<LibjingleStreamTransport>,
@@ -142,8 +125,9 @@ LibjingleStreamTransport::LibjingleStreamTransport(
: port_allocator_(port_allocator),
network_settings_(network_settings),
event_handler_(NULL),
- ice_username_fragment_(CreateRandomString(cricket::ICE_UFRAG_LENGTH)),
- ice_password_(CreateRandomString(cricket::ICE_PWD_LENGTH)),
+ ice_username_fragment_(
+ talk_base::CreateRandomString(cricket::ICE_UFRAG_LENGTH)),
+ ice_password_(talk_base::CreateRandomString(cricket::ICE_PWD_LENGTH)),
can_start_(false),
channel_was_writable_(false),
connect_attempts_left_(kMaxReconnectAttempts) {
@@ -386,7 +370,7 @@ void LibjingleStreamTransport::TryReconnect() {
--connect_attempts_left_;
// Restart ICE by resetting ICE password.
- ice_password_ = CreateRandomString(cricket::ICE_PWD_LENGTH);
+ ice_password_ = talk_base::CreateRandomString(cricket::ICE_PWD_LENGTH);
channel_->SetIceCredentials(ice_username_fragment_, ice_password_);
}
@@ -424,7 +408,6 @@ LibjingleTransportFactory::LibjingleTransportFactory(
: signal_strategy_(signal_strategy),
port_allocator_(port_allocator.Pass()),
network_settings_(network_settings) {
- jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
}
LibjingleTransportFactory::~LibjingleTransportFactory() {
diff --git a/remoting/protocol/monitored_video_stub.h b/remoting/protocol/monitored_video_stub.h
index bce6cb7709..713878c48d 100644
--- a/remoting/protocol/monitored_video_stub.h
+++ b/remoting/protocol/monitored_video_stub.h
@@ -31,7 +31,7 @@ class MonitoredVideoStub : public VideoStub {
// destroy the MonitoredVideoStub object.
typedef base::Callback<void(bool connected)> ChannelStateCallback;
- static const int kConnectivityCheckDelaySeconds = 1;
+ static const int kConnectivityCheckDelaySeconds = 2;
MonitoredVideoStub(
VideoStub* video_stub,
diff --git a/remoting/protocol/session_config.cc b/remoting/protocol/session_config.cc
index bee25d001f..1728a80812 100644
--- a/remoting/protocol/session_config.cc
+++ b/remoting/protocol/session_config.cc
@@ -49,7 +49,7 @@ bool SessionConfig::SupportsCapabilities() const {
SessionConfig SessionConfig::ForTest() {
SessionConfig result;
result.set_control_config(ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
- kControlStreamVersionNoCapabilities,
+ kControlStreamVersion,
ChannelConfig::CODEC_UNDEFINED));
result.set_event_config(ChannelConfig(ChannelConfig::TRANSPORT_MUX_STREAM,
kDefaultStreamVersion,
@@ -129,12 +129,12 @@ bool CandidateSessionConfig::GetFinalConfig(SessionConfig* result) const {
// static
bool CandidateSessionConfig::SelectCommonChannelConfig(
- const std::vector<ChannelConfig>& host_configs,
- const std::vector<ChannelConfig>& client_configs,
+ const std::list<ChannelConfig>& host_configs,
+ const std::list<ChannelConfig>& client_configs,
ChannelConfig* config) {
// Usually each of these vectors will contain just several elements,
// so iterating over all of them is not a problem.
- std::vector<ChannelConfig>::const_iterator it;
+ std::list<ChannelConfig>::const_iterator it;
for (it = client_configs.begin(); it != client_configs.end(); ++it) {
if (IsChannelConfigSupported(host_configs, *it)) {
*config = *it;
@@ -146,7 +146,7 @@ bool CandidateSessionConfig::SelectCommonChannelConfig(
// static
bool CandidateSessionConfig::IsChannelConfigSupported(
- const std::vector<ChannelConfig>& vector,
+ const std::list<ChannelConfig>& vector,
const ChannelConfig& value) {
return std::find(vector.begin(), vector.end(), value) != vector.end();
}
@@ -195,10 +195,6 @@ scoped_ptr<CandidateSessionConfig> CandidateSessionConfig::CreateDefault() {
result->mutable_video_configs()->push_back(
ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
kDefaultStreamVersion,
- ChannelConfig::CODEC_VP9));
- result->mutable_video_configs()->push_back(
- ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
- kDefaultStreamVersion,
ChannelConfig::CODEC_VP8));
// Audio channel.
@@ -211,26 +207,16 @@ scoped_ptr<CandidateSessionConfig> CandidateSessionConfig::CreateDefault() {
return result.Pass();
}
-// static
-void CandidateSessionConfig::DisableAudioChannel(
- CandidateSessionConfig* config) {
- config->mutable_audio_configs()->clear();
- config->mutable_audio_configs()->push_back(ChannelConfig());
+void CandidateSessionConfig::DisableAudioChannel() {
+ mutable_audio_configs()->clear();
+ mutable_audio_configs()->push_back(ChannelConfig());
}
-// static
-void CandidateSessionConfig::DisableVideoCodec(
- CandidateSessionConfig* config,
- ChannelConfig::Codec codec) {
- std ::vector<ChannelConfig>::iterator i;
- for (i = config->mutable_video_configs()->begin();
- i != config->mutable_video_configs()->end();) {
- if (i->codec == codec) {
- i = config->mutable_video_configs()->erase(i);
- } else {
- ++i;
- }
- }
+void CandidateSessionConfig::EnableVideoCodec(ChannelConfig::Codec codec) {
+ mutable_video_configs()->push_front(
+ ChannelConfig(ChannelConfig::TRANSPORT_STREAM,
+ kDefaultStreamVersion,
+ codec));
}
} // namespace protocol
diff --git a/remoting/protocol/session_config.h b/remoting/protocol/session_config.h
index b3fa90a511..ae1ab5eff4 100644
--- a/remoting/protocol/session_config.h
+++ b/remoting/protocol/session_config.h
@@ -5,8 +5,8 @@
#ifndef REMOTING_PROTOCOL_SESSION_CONFIG_H_
#define REMOTING_PROTOCOL_SESSION_CONFIG_H_
+#include <list>
#include <string>
-#include <vector>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
@@ -48,7 +48,7 @@ struct ChannelConfig {
ChannelConfig(TransportType transport, int version, Codec codec);
// operator== is overloaded so that std::find() works with
- // std::vector<ChannelConfig>.
+ // std::list<ChannelConfig>.
bool operator==(const ChannelConfig& b) const;
TransportType transport;
@@ -101,37 +101,42 @@ class SessionConfig {
// because it allows one to specify multiple configurations for each channel.
class CandidateSessionConfig {
public:
+ static scoped_ptr<CandidateSessionConfig> CreateEmpty();
+ static scoped_ptr<CandidateSessionConfig> CreateFrom(
+ const SessionConfig& config);
+ static scoped_ptr<CandidateSessionConfig> CreateDefault();
+
~CandidateSessionConfig();
- const std::vector<ChannelConfig>& control_configs() const {
+ const std::list<ChannelConfig>& control_configs() const {
return control_configs_;
}
- std::vector<ChannelConfig>* mutable_control_configs() {
+ std::list<ChannelConfig>* mutable_control_configs() {
return &control_configs_;
}
- const std::vector<ChannelConfig>& event_configs() const {
+ const std::list<ChannelConfig>& event_configs() const {
return event_configs_;
}
- std::vector<ChannelConfig>* mutable_event_configs() {
+ std::list<ChannelConfig>* mutable_event_configs() {
return &event_configs_;
}
- const std::vector<ChannelConfig>& video_configs() const {
+ const std::list<ChannelConfig>& video_configs() const {
return video_configs_;
}
- std::vector<ChannelConfig>* mutable_video_configs() {
+ std::list<ChannelConfig>* mutable_video_configs() {
return &video_configs_;
}
- const std::vector<ChannelConfig>& audio_configs() const {
+ const std::list<ChannelConfig>& audio_configs() const {
return audio_configs_;
}
- std::vector<ChannelConfig>* mutable_audio_configs() {
+ std::list<ChannelConfig>* mutable_audio_configs() {
return &audio_configs_;
}
@@ -153,15 +158,9 @@ class CandidateSessionConfig {
scoped_ptr<CandidateSessionConfig> Clone() const;
- static scoped_ptr<CandidateSessionConfig> CreateEmpty();
- static scoped_ptr<CandidateSessionConfig> CreateFrom(
- const SessionConfig& config);
- static scoped_ptr<CandidateSessionConfig> CreateDefault();
-
- // Modifies |config| to disable specific features.
- static void DisableAudioChannel(CandidateSessionConfig* config);
- static void DisableVideoCodec(CandidateSessionConfig* config,
- ChannelConfig::Codec codec);
+ // Helpers for enabling/disabling specific features.
+ void DisableAudioChannel();
+ void EnableVideoCodec(ChannelConfig::Codec codec);
private:
CandidateSessionConfig();
@@ -169,16 +168,16 @@ class CandidateSessionConfig {
CandidateSessionConfig& operator=(const CandidateSessionConfig& b);
static bool SelectCommonChannelConfig(
- const std::vector<ChannelConfig>& host_configs_,
- const std::vector<ChannelConfig>& client_configs_,
+ const std::list<ChannelConfig>& host_configs_,
+ const std::list<ChannelConfig>& client_configs_,
ChannelConfig* config);
- static bool IsChannelConfigSupported(const std::vector<ChannelConfig>& vector,
+ static bool IsChannelConfigSupported(const std::list<ChannelConfig>& list,
const ChannelConfig& value);
- std::vector<ChannelConfig> control_configs_;
- std::vector<ChannelConfig> event_configs_;
- std::vector<ChannelConfig> video_configs_;
- std::vector<ChannelConfig> audio_configs_;
+ std::list<ChannelConfig> control_configs_;
+ std::list<ChannelConfig> event_configs_;
+ std::list<ChannelConfig> video_configs_;
+ std::list<ChannelConfig> audio_configs_;
};
} // namespace protocol
diff --git a/remoting/protocol/ssl_hmac_channel_authenticator.cc b/remoting/protocol/ssl_hmac_channel_authenticator.cc
index 7e45acd281..d85ad5f17e 100644
--- a/remoting/protocol/ssl_hmac_channel_authenticator.cc
+++ b/remoting/protocol/ssl_hmac_channel_authenticator.cc
@@ -15,6 +15,7 @@
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/ssl_client_socket.h"
+#include "net/socket/ssl_client_socket_openssl.h"
#include "net/socket/ssl_server_socket.h"
#include "net/ssl/ssl_config_service.h"
#include "remoting/base/rsa_key_pair.h"
@@ -63,6 +64,12 @@ void SslHmacChannelAuthenticator::SecureAndAuthenticate(
int result;
if (is_ssl_server()) {
+#if defined(OS_NACL)
+ // Client plugin doesn't use server SSL sockets, and so SSLServerSocket
+ // implementation is not compiled for NaCl as part of net_nacl.
+ NOTREACHED();
+ result = net::ERR_FAILED;
+#else
scoped_refptr<net::X509Certificate> cert =
net::X509Certificate::CreateFromBytes(
local_cert_.data(), local_cert_.length());
@@ -85,6 +92,7 @@ void SslHmacChannelAuthenticator::SecureAndAuthenticate(
result = raw_server_socket->Handshake(
base::Bind(&SslHmacChannelAuthenticator::OnConnected,
base::Unretained(this)));
+#endif
} else {
transport_security_state_.reset(new net::TransportSecurityState);
@@ -104,11 +112,19 @@ void SslHmacChannelAuthenticator::SecureAndAuthenticate(
net::HostPortPair host_and_port(kSslFakeHostName, 0);
net::SSLClientSocketContext context;
context.transport_security_state = transport_security_state_.get();
- scoped_ptr<net::ClientSocketHandle> connection(new net::ClientSocketHandle);
- connection->SetSocket(socket.Pass());
+ scoped_ptr<net::ClientSocketHandle> socket_handle(
+ new net::ClientSocketHandle);
+ socket_handle->SetSocket(socket.Pass());
+
+#if defined(OS_NACL)
+ // net_nacl doesn't include ClientSocketFactory.
+ socket_.reset(new net::SSLClientSocketOpenSSL(
+ socket_handle.Pass(), host_and_port, ssl_config, context));
+#else
socket_ =
net::ClientSocketFactory::GetDefaultFactory()->CreateSSLClientSocket(
- connection.Pass(), host_and_port, ssl_config, context);
+ socket_handle.Pass(), host_and_port, ssl_config, context);
+#endif
result = socket_->Connect(
base::Bind(&SslHmacChannelAuthenticator::OnConnected,
diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp
index 128ce3191d..0e2ffc8c72 100644
--- a/remoting/remoting.gyp
+++ b/remoting/remoting.gyp
@@ -206,6 +206,7 @@
'webapp/manifest.json.jinja2',
'webapp/paired_client_manager.js',
'webapp/remoting.js',
+ 'webapp/window_frame.js',
],
},
'actions': [
diff --git a/remoting/remoting_android.gypi b/remoting/remoting_android.gypi
index d0865d6d2c..fdfc4e021b 100644
--- a/remoting/remoting_android.gypi
+++ b/remoting/remoting_android.gypi
@@ -14,7 +14,6 @@
],
'variables': {
'jni_gen_package': 'remoting',
- 'jni_generator_ptr_type': 'long',
},
'includes': [ '../build/jni_generator.gypi' ],
}, # end of target 'remoting_jni_headers'
@@ -135,7 +134,7 @@
], # end of 'targets'
}], # 'OS=="android"'
- ['OS=="android" and gtest_target_type=="shared_library"', {
+ ['OS=="android"', {
'targets': [
{
'target_name': 'remoting_unittests_apk',
@@ -149,6 +148,6 @@
'includes': [ '../build/apk_test.gypi' ],
},
],
- }], # 'OS=="android" and gtest_target_type=="shared_library"'
+ }], # 'OS=="android"
], # end of 'conditions'
}
diff --git a/remoting/remoting_client.gypi b/remoting/remoting_client.gypi
index e9d5b32db0..241b0c8761 100644
--- a/remoting/remoting_client.gypi
+++ b/remoting/remoting_client.gypi
@@ -25,13 +25,6 @@
'client/plugin/pepper_entrypoints.cc',
'client/plugin/pepper_entrypoints.h',
],
- 'conditions' : [
- [ 'chromeos==0', {
- 'sources!': [
- 'client/plugin/normalizing_input_filter_cros.cc',
- ],
- }],
- ],
}, # end of target 'remoting_client_plugin'
{
@@ -101,6 +94,13 @@
'remoting_webapp_v1',
'remoting_webapp_v2',
],
+ 'conditions': [
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'dependencies': [
+ 'remoting_webapp_pnacl',
+ ],
+ }],
+ ],
}, # end of target 'remoting_webapp'
{
@@ -127,4 +127,23 @@
'includes': [ 'remoting_webapp.gypi', ],
}, # end of target 'remoting_webapp_v2'
], # end of targets
+
+ 'conditions': [
+ ['disable_nacl==0 and disable_nacl_untrusted==0', {
+ 'targets': [
+ {
+ 'target_name': 'remoting_webapp_pnacl',
+ 'type': 'none',
+ 'variables': {
+ 'output_dir': '<(PRODUCT_DIR)/remoting/remoting.webapp.pnacl',
+ 'zip_path': '<(PRODUCT_DIR)/remoting-webapp-pnacl.zip',
+ 'extra_files': [ 'webapp/background.js' ],
+ 'webapp_type': 'v2_pnacl',
+ },
+ 'includes': [ 'remoting_webapp.gypi', ],
+ }, # end of target 'remoting_webapp_pnacl'
+ ],
+ }],
+ ],
+
}
diff --git a/remoting/remoting_host.gypi b/remoting/remoting_host.gypi
index ff1996f7a9..e5247ea9db 100644
--- a/remoting/remoting_host.gypi
+++ b/remoting/remoting_host.gypi
@@ -119,10 +119,10 @@
'host/disconnect_window_win.cc',
'host/dns_blackhole_checker.cc',
'host/dns_blackhole_checker.h',
+ 'host/gnubby_auth_handler.h',
'host/gnubby_auth_handler_posix.cc',
'host/gnubby_auth_handler_posix.h',
'host/gnubby_auth_handler_win.cc',
- 'host/gnubby_auth_handler.h',
'host/gnubby_socket.cc',
'host/gnubby_socket.h',
'host/heartbeat_sender.cc',
@@ -137,6 +137,8 @@
'host/host_exit_codes.cc',
'host/host_exit_codes.h',
'host/host_export.h',
+ 'host/host_extension.h',
+ 'host/host_extension_session.h',
'host/host_secret.cc',
'host/host_secret.h',
'host/host_status_monitor.h',
@@ -221,8 +223,8 @@
'host/screen_controls.h',
'host/screen_resolution.cc',
'host/screen_resolution.h',
- 'host/server_log_entry.cc',
- 'host/server_log_entry.h',
+ 'host/server_log_entry_host.cc',
+ 'host/server_log_entry_host.h',
'host/service_urls.cc',
'host/service_urls.h',
'host/session_manager_factory.cc',
diff --git a/remoting/remoting_nacl.gyp b/remoting/remoting_nacl.gyp
new file mode 100644
index 0000000000..81dc8da902
--- /dev/null
+++ b/remoting/remoting_nacl.gyp
@@ -0,0 +1,247 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+ 'includes': [
+ '../native_client/build/untrusted.gypi',
+ 'remoting_srcs.gypi',
+ ],
+
+ 'variables': {
+ 'protoc': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)',
+ 'proto_out_base': '<(SHARED_INTERMEDIATE_DIR)/protoc_out',
+ 'proto_out_dir': '<(proto_out_base)/remoting/proto',
+ 'use_nss': 0,
+ 'nacl_untrusted_build': 1,
+ 'chromium_code': 1,
+ },
+
+ 'targets': [
+ {
+ 'target_name': 'remoting_webrtc_nacl',
+ 'type': 'none',
+ 'variables': {
+ 'nacl_untrusted_build': 1,
+ 'nlib_target': 'libremoting_webrtc_nacl.a',
+ 'build_glibc': 0,
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ },
+ 'include_dirs': [
+ '../third_party',
+ '../third_party/webrtc',
+ ],
+ 'sources': [
+ '../third_party/webrtc/modules/desktop_capture/desktop_frame.cc',
+ '../third_party/webrtc/modules/desktop_capture/desktop_frame.h',
+ '../third_party/webrtc/modules/desktop_capture/desktop_geometry.cc',
+ '../third_party/webrtc/modules/desktop_capture/desktop_geometry.h',
+ '../third_party/webrtc/modules/desktop_capture/desktop_region.cc',
+ '../third_party/webrtc/modules/desktop_capture/desktop_region.h',
+ '../third_party/webrtc/modules/desktop_capture/shared_desktop_frame.cc',
+ '../third_party/webrtc/modules/desktop_capture/shared_desktop_frame.h',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '../third_party',
+ '../third_party/webrtc',
+ ],
+ }
+ }, # end of target 'remoting_webrtc_nacl'
+
+ {
+ 'target_name': 'remoting_proto_nacl',
+ 'type': 'none',
+ 'variables': {
+ 'nacl_untrusted_build': 1,
+ 'nlib_target': 'libremoting_proto_nacl.a',
+ 'build_glibc': 0,
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ 'files_list': [
+ '<(proto_out_dir)/audio.pb.cc',
+ '<(proto_out_dir)/control.pb.cc',
+ '<(proto_out_dir)/event.pb.cc',
+ '<(proto_out_dir)/internal.pb.cc',
+ '<(proto_out_dir)/video.pb.cc',
+ '<(proto_out_dir)/mux.pb.cc',
+ ],
+ 'extra_deps': [ '<@(files_list)' ],
+ 'extra_args': [ '<@(files_list)' ],
+ },
+ 'defines': [
+ 'GOOGLE_PROTOBUF_HOST_ARCH_64_BIT=1'
+ ],
+ 'dependencies': [
+ '../native_client/tools.gyp:prep_toolchain',
+ '../third_party/protobuf/protobuf_nacl.gyp:protobuf_lite_nacl',
+ 'proto/chromotocol.gyp:chromotocol_proto_lib',
+ ],
+ 'export_dependent_settings': [
+ '../third_party/protobuf/protobuf_nacl.gyp:protobuf_lite_nacl',
+ ],
+ 'direct_dependent_settings': {
+ 'include_dirs': [
+ '<(proto_out_base)',
+ ],
+ },
+ }, # end of target 'remoting_proto_nacl'
+
+ {
+ 'target_name': 'remoting_client_plugin_lib_nacl',
+ 'type': 'none',
+ 'variables': {
+ 'nacl_untrusted_build': 1,
+ 'nlib_target': 'libremoting_client_plugin_lib_nacl.a',
+ 'build_glibc': 0,
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ },
+ 'dependencies': [
+ '../base/base_nacl.gyp:base_nacl',
+ '../jingle/jingle_nacl.gyp:jingle_glue_nacl',
+ '../native_client/tools.gyp:prep_toolchain',
+ '../native_client_sdk/native_client_sdk_untrusted.gyp:nacl_io_untrusted',
+ '../net/net_nacl.gyp:net_nacl',
+ '../third_party/libjingle/libjingle_nacl.gyp:libjingle_nacl',
+ '../third_party/libvpx/libvpx_nacl.gyp:libvpx_nacl',
+ '../third_party/libwebm/libwebm_nacl.gyp:libwebm_nacl',
+ '../third_party/libyuv/libyuv_nacl.gyp:libyuv_nacl',
+ '../third_party/openssl/openssl_nacl.gyp:openssl_nacl',
+ '../third_party/opus/opus_nacl.gyp:opus_nacl',
+ 'remoting_proto_nacl',
+ 'remoting_webrtc_nacl',
+ ],
+ 'sources': [
+ '../ui/events/keycodes/dom4/keycode_converter.cc',
+ '<@(remoting_base_sources)',
+ '<@(remoting_client_plugin_sources)',
+ '<@(remoting_client_sources)',
+ '<@(remoting_protocol_sources)',
+ ],
+ 'sources!': [
+ 'base/url_request_context.cc',
+ 'jingle_glue/chromium_socket_factory.cc',
+ ],
+
+ # Include normalizing_input_filter_mac.cc excluded by the filename
+ # exclusion rules. Must be in target_conditions to make sure it's
+ # evaluated after the filename rules.
+ 'target_conditions': [
+ ['1==1', {
+ 'sources/': [
+ [ 'include', 'client/plugin/normalizing_input_filter_mac.cc' ],
+ ],
+ }],
+ ],
+ }, # end of target 'remoting_client_plugin_lib_nacl'
+
+ {
+ 'target_name': 'remoting_client_plugin_nacl',
+ 'type': 'none',
+ 'variables': {
+ 'nacl_untrusted_build': 1,
+ 'nexe_target': 'remoting_client_plugin',
+ 'build_glibc': 0,
+ 'build_newlib': 0,
+ 'build_pnacl_newlib': 1,
+ 'enable_x86_32': 0,
+ 'enable_x86_64': 0,
+ 'extra_deps_pnacl_newlib': [
+ '>(tc_lib_dir_pnacl_newlib)/libbase_i18n_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libbase_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libexpat_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libicudata_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libcrypto_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libicui18n_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libicuuc_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libjingle_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libjingle_p2p_constants_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libmedia_yuv_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libmodp_b64_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libopenssl_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libopus_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libppapi.a',
+ '>(tc_lib_dir_pnacl_newlib)/libppapi_cpp.a',
+ '>(tc_lib_dir_pnacl_newlib)/libprotobuf_lite_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libjingle_glue_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libnet_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libremoting_client_plugin_lib_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libremoting_proto_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libremoting_webrtc_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/liburl_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libvpx_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libwebm_nacl.a',
+ '>(tc_lib_dir_pnacl_newlib)/libyuv_nacl.a',
+ ],
+ },
+ 'dependencies': [
+ '../base/base_nacl.gyp:base_i18n_nacl',
+ '../base/base_nacl.gyp:base_nacl',
+ '../crypto/crypto_nacl.gyp:crypto_nacl',
+ '../jingle/jingle_nacl.gyp:jingle_glue_nacl',
+ '../media/media_nacl.gyp:media_yuv_nacl',
+ '../native_client/tools.gyp:prep_toolchain',
+ '../native_client_sdk/native_client_sdk_untrusted.gyp:nacl_io_untrusted',
+ '../net/net_nacl.gyp:net_nacl',
+ '../ppapi/native_client/native_client.gyp:nacl_irt',
+ '../ppapi/native_client/native_client.gyp:ppapi_lib',
+ '../ppapi/ppapi_nacl.gyp:ppapi_cpp_lib',
+ '../third_party/expat/expat_nacl.gyp:expat_nacl',
+ '../third_party/icu/icu_nacl.gyp:icudata_nacl',
+ '../third_party/icu/icu_nacl.gyp:icui18n_nacl',
+ '../third_party/icu/icu_nacl.gyp:icuuc_nacl',
+ '../third_party/libjingle/libjingle_nacl.gyp:libjingle_nacl',
+ '../third_party/libwebm/libwebm_nacl.gyp:libwebm_nacl',
+ '../third_party/libyuv/libyuv_nacl.gyp:libyuv_nacl',
+ '../third_party/modp_b64/modp_b64_nacl.gyp:modp_b64_nacl',
+ '../third_party/openssl/openssl_nacl.gyp:openssl_nacl',
+ '../url/url_nacl.gyp:url_nacl',
+ 'remoting_client_plugin_lib_nacl',
+ 'remoting_proto_nacl',
+ 'remoting_webrtc_nacl',
+ ],
+ 'link_flags': [
+ '-lppapi_stub',
+
+ # Plugin code.
+ '-lremoting_client_plugin_lib_nacl',
+ '-lremoting_proto_nacl',
+
+ # Chromium libraries.
+ '-ljingle_glue_nacl',
+ '-lmedia_yuv_nacl',
+ '-lnet_nacl',
+ '-lcrypto_nacl',
+ '-lbase_i18n_nacl',
+ '-lbase_nacl',
+ '-lurl_nacl',
+
+ # Third-party libraries.
+ '-lremoting_webrtc_nacl',
+ '-lyuv_nacl',
+ '-lvpx_nacl',
+ '-ljingle_p2p_constants_nacl',
+ '-ljingle_nacl',
+ '-lexpat_nacl',
+ '-lmodp_b64_nacl',
+ '-lopus_nacl',
+ '-lopenssl_nacl',
+ '-licui18n_nacl',
+ '-licuuc_nacl',
+ '-licudata_nacl',
+ '-lprotobuf_lite_nacl',
+ '-lwebm_nacl',
+
+ # Base NaCl libraries.
+ '-lppapi_cpp',
+ '-lpthread',
+ '-lnacl_io',
+ ],
+ 'sources': [
+ 'client/plugin/pepper_module.cc',
+ ],
+ }, # end of target 'remoting_client_plugin_nacl'
+ ]
+}
diff --git a/remoting/remoting_srcs.gypi b/remoting/remoting_srcs.gypi
index d9d663e1fa..25785e209b 100644
--- a/remoting/remoting_srcs.gypi
+++ b/remoting/remoting_srcs.gypi
@@ -75,6 +75,8 @@
'jingle_glue/jingle_info_request.h',
'jingle_glue/network_settings.cc',
'jingle_glue/network_settings.h',
+ 'jingle_glue/server_log_entry.cc',
+ 'jingle_glue/server_log_entry.h',
'jingle_glue/signal_strategy.h',
'jingle_glue/xmpp_signal_strategy.cc',
'jingle_glue/xmpp_signal_strategy.h',
@@ -212,8 +214,8 @@
'client/key_event_mapper.h',
'client/log_to_server.cc',
'client/log_to_server.h',
- 'client/server_log_entry.cc',
- 'client/server_log_entry.h',
+ 'client/server_log_entry_client.cc',
+ 'client/server_log_entry_client.h',
'client/software_video_renderer.cc',
'client/software_video_renderer.h',
'client/video_renderer.h',
@@ -226,10 +228,10 @@
'client/plugin/delegating_signal_strategy.h',
'client/plugin/media_source_video_renderer.cc',
'client/plugin/media_source_video_renderer.h',
- 'client/plugin/normalizing_input_filter.cc',
- 'client/plugin/normalizing_input_filter.h',
'client/plugin/normalizing_input_filter_cros.cc',
+ 'client/plugin/normalizing_input_filter_cros.h',
'client/plugin/normalizing_input_filter_mac.cc',
+ 'client/plugin/normalizing_input_filter_mac.h',
'client/plugin/pepper_audio_player.cc',
'client/plugin/pepper_audio_player.h',
'client/plugin/pepper_input_handler.cc',
diff --git a/remoting/remoting_test.gypi b/remoting/remoting_test.gypi
index f5fc9d6ff1..07e0efe709 100644
--- a/remoting/remoting_test.gypi
+++ b/remoting/remoting_test.gypi
@@ -17,6 +17,7 @@
'../ppapi/ppapi.gyp:ppapi_cpp',
'../testing/gmock.gyp:gmock',
'../testing/gtest.gyp:gtest',
+ '../third_party/libyuv/libyuv.gyp:libyuv',
'../third_party/webrtc/modules/modules.gyp:desktop_capture',
'../ui/base/ui_base.gyp:ui_base',
'../ui/gfx/gfx.gyp:gfx',
@@ -55,6 +56,7 @@
'base/util_unittest.cc',
'client/audio_player_unittest.cc',
'client/key_event_mapper_unittest.cc',
+ 'client/server_log_entry_client_unittest.cc',
'client/plugin/normalizing_input_filter_cros_unittest.cc',
'client/plugin/normalizing_input_filter_mac_unittest.cc',
'codec/audio_encoder_opus_unittest.cc',
@@ -99,12 +101,12 @@
'host/register_support_host_request_unittest.cc',
'host/remote_input_filter_unittest.cc',
'host/resizing_host_observer_unittest.cc',
- 'host/setup/me2me_native_messaging_host.cc',
- 'host/setup/me2me_native_messaging_host.h',
'host/screen_capturer_fake.cc',
'host/screen_capturer_fake.h',
'host/screen_resolution_unittest.cc',
- 'host/server_log_entry_unittest.cc',
+ 'host/server_log_entry_host_unittest.cc',
+ 'host/setup/me2me_native_messaging_host.cc',
+ 'host/setup/me2me_native_messaging_host.h',
'host/setup/me2me_native_messaging_host_unittest.cc',
'host/setup/oauth_helper_unittest.cc',
'host/setup/pin_validator_unittest.cc',
@@ -122,6 +124,8 @@
'jingle_glue/mock_objects.cc',
'jingle_glue/mock_objects.h',
'jingle_glue/network_settings_unittest.cc',
+ 'jingle_glue/server_log_entry_unittest.cc',
+ 'jingle_glue/server_log_entry_unittest.h',
'protocol/authenticator_test_base.cc',
'protocol/authenticator_test_base.h',
'protocol/buffered_socket_writer_unittest.cc',
@@ -207,7 +211,7 @@
'remoting_client_plugin',
],
}],
- [ 'OS=="android" and gtest_target_type=="shared_library"', {
+ [ 'OS=="android"', {
'dependencies': [
'../testing/android/native_test.gyp:native_test_native_code',
],
diff --git a/remoting/remoting_webapp.gypi b/remoting/remoting_webapp.gypi
index e1bce7afe4..871a876af6 100644
--- a/remoting/remoting_webapp.gypi
+++ b/remoting/remoting_webapp.gypi
@@ -35,6 +35,17 @@
'plugin_args': [],
},
}],
+ ['webapp_type=="v2_pnacl"', {
+ 'dependencies': [
+ 'remoting_nacl.gyp:remoting_client_plugin_nacl',
+ ],
+ 'variables': {
+ 'extra_files': [
+ 'webapp/remoting_client_pnacl.nmf',
+ '<(PRODUCT_DIR)/remoting_client_plugin_newlib.pexe',
+ ],
+ },
+ }],
['run_jscompile != 0', {
'variables': {
'success_stamp': '<(PRODUCT_DIR)/remoting_webapp_jscompile.stamp',
diff --git a/remoting/remoting_webapp_files.gypi b/remoting/remoting_webapp_files.gypi
index c862a13c58..ba94d40810 100644
--- a/remoting/remoting_webapp_files.gypi
+++ b/remoting/remoting_webapp_files.gypi
@@ -90,6 +90,7 @@
'webapp/menu_button.js',
'webapp/ui_mode.js',
'webapp/toolbar.js',
+ 'webapp/window_frame.js',
],
# UI files for controlling the local machine as a host.
'remoting_webapp_js_ui_host_control_files': [
@@ -123,6 +124,9 @@
# browser test JavaScript files.
'remoting_webapp_js_browser_test_files': [
'webapp/browser_test/browser_test.js',
+ 'webapp/browser_test/cancel_pin_browser_test.js',
+ 'webapp/browser_test/invalid_pin_browser_test.js',
+ 'webapp/browser_test/update_pin_browser_test.js',
],
# The JavaScript files required by main.html.
'remoting_webapp_main_html_js_files': [
@@ -166,10 +170,15 @@
'remoting_webapp_resource_files': [
'resources/disclosure_arrow_down.webp',
'resources/disclosure_arrow_right.webp',
+ 'resources/drag.webp',
'resources/host_setup_instructions.webp',
+ 'resources/icon_close.webp',
'resources/icon_cross.webp',
+ 'resources/icon_disconnect.webp',
'resources/icon_help.webp',
'resources/icon_host.webp',
+ 'resources/icon_maximize_restore.webp',
+ 'resources/icon_minimize.webp',
'resources/icon_pencil.webp',
'resources/icon_warning.webp',
'resources/infographic_my_computers.webp',
@@ -185,6 +194,7 @@
'webapp/scale-to-fit.webp',
'webapp/spinner.gif',
'webapp/toolbar.css',
+ 'webapp/window_frame.css',
],
'remoting_webapp_files': [
@@ -222,6 +232,7 @@
'webapp/html/ui_header.html',
'webapp/html/ui_it2me.html',
'webapp/html/ui_me2me.html',
+ 'webapp/html/window_frame.html',
],
},
diff --git a/remoting/resources/drag.webp b/remoting/resources/drag.webp
new file mode 100644
index 0000000000..e93f0efcea
--- /dev/null
+++ b/remoting/resources/drag.webp
Binary files differ
diff --git a/remoting/resources/icon_close.webp b/remoting/resources/icon_close.webp
new file mode 100644
index 0000000000..65ee17c2c0
--- /dev/null
+++ b/remoting/resources/icon_close.webp
Binary files differ
diff --git a/remoting/resources/icon_disconnect.webp b/remoting/resources/icon_disconnect.webp
new file mode 100644
index 0000000000..ac23d83642
--- /dev/null
+++ b/remoting/resources/icon_disconnect.webp
Binary files differ
diff --git a/remoting/resources/icon_maximize_restore.webp b/remoting/resources/icon_maximize_restore.webp
new file mode 100644
index 0000000000..a91c1cc377
--- /dev/null
+++ b/remoting/resources/icon_maximize_restore.webp
Binary files differ
diff --git a/remoting/resources/icon_minimize.webp b/remoting/resources/icon_minimize.webp
new file mode 100644
index 0000000000..a21cad0a77
--- /dev/null
+++ b/remoting/resources/icon_minimize.webp
Binary files differ
diff --git a/remoting/resources/remoting_strings.grd b/remoting/resources/remoting_strings.grd
index b9d8bc267e..2161914e26 100644
--- a/remoting/resources/remoting_strings.grd
+++ b/remoting/resources/remoting_strings.grd
@@ -552,9 +552,24 @@ For information about privacy, please see the Google Privacy Policy (http://goo.
<message desc="Footer text displayed at the host after an access code has been generated, but before a client connects." name="IDS_FOOTER_WAITING">
waiting for connection…
</message>
- <message desc="Menu option for toggle full-screen mode. Equivalent to using the Wrench menu to do the same thing." name="IDS_FULL_SCREEN" formatter_data="android_java">
+ <message desc="Menu option for toggle full-screen mode." name="IDS_FULL_SCREEN" formatter_data="android_java">
Full screen
</message>
+ <message desc="Icon to leave full-screen mode." name="IDS_EXIT_FULL_SCREEN">
+ Exit full screen
+ </message>
+ <message desc="Tool-tip for the window's close icon." name="IDS_CLOSE_WINDOW">
+ Close window
+ </message>
+ <message desc="Tool-tip for the window's maximize icon." name="IDS_MAXIMIZE_WINDOW">
+ Maximize window
+ </message>
+ <message desc="Tool-tip for the window's minimize icon." name="IDS_MINIMIZE_WINDOW">
+ Minimize window
+ </message>
+ <message desc="Tool-tip for the window's restore icon." name="IDS_RESTORE_WINDOW">
+ Restore window
+ </message>
<message desc="Button displayed underneath explanatory text for app features. Clicking causes the text, infographic and the button itself to be replaced by the actual UI for that feature." name="IDS_GET_STARTED">
Get started
</message>
diff --git a/remoting/resources/remoting_strings_ar.xtb b/remoting/resources/remoting_strings_ar.xtb
index e440490c0e..b62d5e4f7c 100644
--- a/remoting/resources/remoting_strings_ar.xtb
+++ b/remoting/resources/remoting_strings_ar.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">â€ØªÙ… اكتشا٠إصدار غير متواÙÙ‚ من سطح المكتب البعيد من Chrome. الرجاء التأكد من تثبيت أحدث إصدار من Chrome وسطح المكتب البعيد من Chrome على جهازي الكمبيوتر كليهما وإعادة المحاولة.</translation>
<translation id="6998989275928107238">إلى</translation>
<translation id="406849768426631008">â€Ø¥Ø°Ø§ كنت ترغب ÙÙŠ تقديم المساعدة إلى شخص ما أثناء إجراء دردشة Ùيديو معه، ÙÙŠÙمكنك تجربة ميزة <ph name="LINK_BEGIN"/> سطح المكتب البعيد ÙÙŠ Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">تكبير الناÙذة</translation>
<translation id="5397086374758643919">â€Ø£Ø¯Ø§Ø© إزالة مضي٠سطح المكتب البعيد من Chrome</translation>
<translation id="3258789396564295715">â€ÙŠÙ…كنك الدخول بأمان إلى هذا الكمبيوتر باستخدام سطح المكتب البعيد من Chrome.</translation>
<translation id="5070121137485264635">â€ÙŠØªØ·Ù„ب منك المضي٠البعيد مصادقة موقع ويب لجهة خارجية. للمتابعة، يجب منح تطبيق سطح المكتب البعيد من Chrome أذونات إضاÙية للدخول إلى هذا العنوان:</translation>
<translation id="2124408767156847088">â€Ø§Ø³ØªÙ…تع بدخول آمن إلى جهاز الكمبيوتر من خلال جهاز Android.</translation>
+<translation id="3194245623920924351">â€Ø³Ø·Ø­ المكتب البعيد من Chrome</translation>
+<translation id="3649256019230929621">تصغير الناÙذة</translation>
<translation id="4808503597364150972">الرجاء إدخال رقم التعري٠الشخصي التابع لك لـ <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">تم تعطيل الاتصالات عن بÙعد لهذا الكمبيوتر.</translation>
<translation id="8244400547700556338">تعرّ٠على الكيÙية.</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">إلغاء</translation>
<translation id="7782471917492991422">يرجى التحقق من إعدادات إدارة الطاقة ÙÙŠ جهاز الكمبيوتر وضمان عدم تهيئتها على وضع السكون عند الخمول.</translation>
<translation id="7665369617277396874">إضاÙØ© حساب</translation>
+<translation id="5925497314631808737">• تم تنÙيذ وضع ملء الشاشة /مجسم.
+• رمز محسن لإخÙاء شريط الإجراءات.</translation>
<translation id="2707879711568641861">â€Ø¨Ø¹Ø¶ المكونات المطلوبة لسطح المكتب البعيد من Chrome Ù…Ùقودة. الرجاء التأكد من تثبيت أحدث إصدار وإعادة المحاولة.</translation>
+<translation id="1779766957982586368">إغلاق الناÙذة</translation>
<translation id="2499160551253595098">â€Ø³Ø§Ø¹Ø¯Ù†Ø§ ÙÙŠ تحسين سطح المكتب البعيد من Chrome من خلال السماح لنا بتجميع إحصاءات الاستخدام وتقارير الأعطال.</translation>
<translation id="7868137160098754906">الرجاء إدخال رقم التعري٠الشخصي لجهاز الكمبيوتر البعيد.</translation>
<translation id="677755392401385740">بدأ المضي٠للمستخدم: <ph name="HOST_USERNAME"/>.</translation>
@@ -135,7 +141,6 @@
برنامج سطح المكتب البعيد من Chrome عليه وانقر على “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">العميل المتصل: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">ÙÙŠ انتظار الاتصال...</translation>
-<translation id="5714695932513333758">سجل الاتصالات</translation>
<translation id="811307782653349804">يمكنك الدخول إلى جهاز الكمبيوتر من أي مكان.</translation>
<translation id="2939145106548231838">مصادقة للاستضاÙØ©</translation>
<translation id="2366718077645204424">يتعذر الوصول إلى المضيÙ. ربما يرجع ذلك إلى تهيئة الشبكة التي تستخدمها.</translation>
@@ -191,7 +196,6 @@
<translation id="5308380583665731573">اتصال</translation>
<translation id="7319983568955948908">إيقا٠المشاركة</translation>
<translation id="6681800064886881394">â€Ø­Ù‚وق الطبع والنشر لعام 2013 لشركة Google Inc. جميع الحقوق محÙوظة.</translation>
-<translation id="8038108135592087998">â€Ø§Ù„إصدار الأول من تطبيق سطح المكتب البعيد من Chrome لأجهزة Android.</translation>
<translation id="6668065415969892472">تم تحديث رقم التعري٠الشخصي.</translation>
<translation id="4513946894732546136">تعليقات</translation>
<translation id="4277736576214464567">رمز الدخول غير صالح، الرجاء إعادة المحاولة.</translation>
@@ -216,5 +220,6 @@
<translation id="1818475040640568770">ليس لديك أجهزة كمبيوتر مسجلة.</translation>
<translation id="4394049700291259645">تعطيل</translation>
<translation id="4156740505453712750">لحماية الدخول إلى جهاز الكمبيوتر هذا، يرجى اختيار رقم تعري٠شخصي مكون من <ph name="BOLD_START"/>ستة أرقام على الأقل<ph name="BOLD_END"/>. وستتم المطالبة برقم التعري٠الشخصي هذا عند الاتصال من موقع آخر.</translation>
+<translation id="6398765197997659313">إنهاء وضع ملء الشاشة</translation>
<translation id="3286521253923406898">â€Ø£Ø¯Ø§Ø© التحكم ÙÙŠ مضي٠التواÙÙ‚ مع نظام التشغيل Chrome</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_bg.xtb b/remoting/resources/remoting_strings_bg.xtb
index d38942ccac..18394570ad 100644
--- a/remoting/resources/remoting_strings_bg.xtb
+++ b/remoting/resources/remoting_strings_bg.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Открита е неÑъвмеÑтима верÑÐ¸Ñ Ð½Ð° Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome. МолÑ, уверете Ñе, че разполагате на двата компютъра Ñ Ð½Ð°Ð¹-новата верÑÐ¸Ñ Ð½Ð° Chrome и на приложението и опитайте пак.</translation>
<translation id="6998989275928107238">До</translation>
<translation id="406849768426631008">ИÑкате ли да помогнете на нÑкого, докато водите и видеоразговор Ñ Ð½ÐµÐ³Ð¾? Изпробвайте <ph name="LINK_BEGIN"/>Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот в Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Увеличаване на прозореца</translation>
<translation id="5397086374758643919">ДеинÑталираща програма за хоÑта на Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome</translation>
<translation id="3258789396564295715">Може да оÑъщеÑтвите надеждно доÑтъп до този компютър поÑредÑтвом Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome.</translation>
<translation id="5070121137485264635">ОтдалечениÑÑ‚ хоÑÑ‚ изиÑква да удоÑтоверите ÑамоличноÑтта Ñи пред уебÑайт на трета Ñтрана. За да продължите, Ñ‚Ñ€Ñбва да предоÑтавите на Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome допълнителни Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð·Ð° доÑтъп до този адреÑ:</translation>
<translation id="2124408767156847088">ОÑъщеÑтвÑвайте Ñигурно доÑтъп до компютрите Ñи от уÑтройÑтвото Ñи Ñ Android.</translation>
+<translation id="3194245623920924351">Отдалечен работен плот</translation>
+<translation id="3649256019230929621">ÐамалÑване на прозореца</translation>
<translation id="4808503597364150972">МолÑ, въведете ПИРкода Ñи за <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Отдалечените връзки за този компютър Ñа деактивирани.</translation>
<translation id="8244400547700556338">Ðаучете как.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Отказ</translation>
<translation id="7782471917492991422">МолÑ, проверете наÑтройките за управление на захранването на компютъра Ñи и Ñе уверете, че не е конфигуриран да преминава в ÑпÑщ режим при неактивноÑÑ‚.</translation>
<translation id="7665369617277396874">ДобавÑне на профил</translation>
+<translation id="5925497314631808737">• Внедрен цÑл екран/режим за виртуална реалноÑÑ‚.
+• Подобрена икона за Ñкриване на лентата за дейÑтвиÑ.</translation>
<translation id="2707879711568641861">ЛипÑват нÑкои компоненти, задължителни за Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome. МолÑ, уверете Ñе, че Ñте инÑталирали най-новата верÑÐ¸Ñ Ð¸ опитайте пак.</translation>
+<translation id="1779766957982586368">ЗатварÑне на прозореца</translation>
<translation id="2499160551253595098">Помогнете ни да подобрим Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome, като ни разрешите да Ñъбираме ÑтатиÑтичеÑки данни за употребата и Ñигнали за Ñривове.</translation>
<translation id="7868137160098754906">МолÑ, въведете ПИРкода Ñи за Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¿ÑŽÑ‚ÑŠÑ€.</translation>
<translation id="677755392401385740">За Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ñ Ðµ Ñтартиран хоÑÑ‚: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Chromoting</translation>
<translation id="4361728918881830843">За да активирате отдалечените връзки към друг компютър, инÑталирайте на него Ð¾Ñ‚Ð´Ð°Ð»ÐµÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚ÐµÐ½ плот на Chrome и кликнете върху „<ph name="BUTTON_NAME"/>“.</translation>
<translation id="7444276978508498879">УÑтановена е връзка Ñ ÐºÐ»Ð¸ÐµÐ½Ñ‚Ñка програма: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">изчаква Ñе връзка…</translation>
-<translation id="5714695932513333758">ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð½Ð° връзките</translation>
<translation id="811307782653349804">ОÑъщеÑтвÑвайте доÑтъп до компютъра Ñи отвÑÑкъде.</translation>
<translation id="2939145106548231838">УдоÑтоверÑване пред хоÑта</translation>
<translation id="2366718077645204424">Ðе можем да Ñе Ñвържем Ñ Ñ…Ð¾Ñта. Това вероÑтно Ñе дължи на конфигурациÑта на използваната от Ð²Ð°Ñ Ð¼Ñ€ÐµÐ¶Ð°.</translation>
@@ -189,7 +194,6 @@ Chromoting</translation>
<translation id="5308380583665731573">Свързване</translation>
<translation id="7319983568955948908">Спиране на ÑподелÑнето</translation>
<translation id="6681800064886881394">ÐвторÑки права 2013 Google Inc. Ð’Ñички права запазени.</translation>
-<translation id="8038108135592087998">Първа верÑÐ¸Ñ Ð½Ð° приложението за Android за отдалечен работен плот на Chrome.</translation>
<translation id="6668065415969892472">ПИРкодът ви е актуализиран.</translation>
<translation id="4513946894732546136">Отзиви</translation>
<translation id="4277736576214464567">Кодът за доÑтъп е невалиден. МолÑ, опитайте отново.</translation>
@@ -214,5 +218,6 @@ Chromoting</translation>
<translation id="1818475040640568770">ÐÑмате региÑтрирани компютри.</translation>
<translation id="4394049700291259645">Деактивиране</translation>
<translation id="4156740505453712750">За да защитите доÑтъпа до този компютър, молÑ, изберете ПИРкод <ph name="BOLD_START"/>поне Ñ ÑˆÐµÑÑ‚ цифри<ph name="BOLD_END"/>. Той ще Ñе изиÑква при Ñвързване от друго меÑтоположение.</translation>
+<translation id="6398765197997659313">Изход от цÑл екран</translation>
<translation id="3286521253923406898">Контролер за хоÑта на Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_ca.xtb b/remoting/resources/remoting_strings_ca.xtb
index c7782b7633..630a0022e5 100644
--- a/remoting/resources/remoting_strings_ca.xtb
+++ b/remoting/resources/remoting_strings_ca.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">S'ha detectat una versió incompatible de l'escriptori remot de Chrome. Assegureu-vos que teniu la versió més recent de Google Chrome i de l'escriptori remot de Chrome als dos ordinadors i torneu-ho a provar.</translation>
<translation id="6998989275928107238">Per a</translation>
<translation id="406849768426631008">Voleu ajudar algú mentre manteniu una videotrucada? Proveu <ph name="LINK_BEGIN"/> Escriptori remot a Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximitza la finestra</translation>
<translation id="5397086374758643919">Programa de desinstal·lació de l'amfitrió de l'escriptori remot de Chrome</translation>
<translation id="3258789396564295715">Podeu accedir de manera segura a aquest ordinador mitjançant l'escriptori remot de Chrome.</translation>
<translation id="5070121137485264635">L'amfitrió remot necessita que us autentiqueu en un lloc web de tercers. Per continuar, cal que concediu permisos addicionals a l'escriptori remot de Chrome per accedir a aquesta adreça:</translation>
<translation id="2124408767156847088">Accediu de manera segura als vostres ordinadors des del dispositiu Android.</translation>
+<translation id="3194245623920924351">Escriptori remot de Chrome</translation>
+<translation id="3649256019230929621">Minimitza la finestra</translation>
<translation id="4808503597364150972">Introduïu el vostre PIN per a <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">S'han desactivat les connexions remotes en aquest ordinador.</translation>
<translation id="8244400547700556338">Més informació</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Cancel·la</translation>
<translation id="7782471917492991422">Comproveu la configuració de gestió d'energia de l'ordinador i assegureu-vos que no s'hagi configurat per estar en mode de baix consum quan estigui inactiu.</translation>
<translation id="7665369617277396874">Afegeix un compte</translation>
+<translation id="5925497314631808737">• S'ha implementat el mode de pantalla completa/envoltant.
+• S'ha millorat la icona per amagar la barra d'accions.</translation>
<translation id="2707879711568641861">Falten alguns components necessaris per a l'escriptori remot de Chrome. Assegureu-vos que heu instal·lat la versió més recent i torneu-ho a provar.</translation>
+<translation id="1779766957982586368">Tanca la finestra</translation>
<translation id="2499160551253595098">Ajudeu-nos a millorar l'escriptori remot de Chrome: permeteu-nos recollir estadístiques d'ús i informes d'error.</translation>
<translation id="7868137160098754906">Introduïu el vostre PIN de l'ordinador remot.</translation>
<translation id="677755392401385740">L'amfitrió ha començat per a l'usuari: <ph name="HOST_USERNAME"/>.</translation>
@@ -95,7 +101,7 @@ Per obtenir informació sobre la privadesa, vegeu la Política de privadesa de G
<translation id="1742469581923031760">S'està connectant...</translation>
<translation id="8998327464021325874">Controlador de l'amfitrió de l'escriptori remot de Chrome</translation>
<translation id="6748108480210050150">De</translation>
-<translation id="3950820424414687140">Inicieu la sessió</translation>
+<translation id="3950820424414687140">Inici de sessió</translation>
<translation id="1291443878853470558">Heu d'activar les connexions remotes si voleu utilitzar Chromoting per accedir a aquest ordinador.</translation>
<translation id="2599300881200251572">Aquest servei permet les connexions entrants de clients de l'escriptori remot de Chrome.</translation>
<translation id="6091564239975589852">Envia les claus</translation>
@@ -134,7 +140,6 @@ Per obtenir informació sobre la privadesa, vegeu la Política de privadesa de G
<translation id="4361728918881830843">Per activar les connexions remotes a un altre ordinador, instal·leu-hi l'escriptori remot de Chrome i feu clic a <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Client connectat: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">s'està esperant la connexió...</translation>
-<translation id="5714695932513333758">Historial de connexions</translation>
<translation id="811307782653349804">Accediu al vostre ordinador des de qualsevol lloc.</translation>
<translation id="2939145106548231838">Autenticació a l'amfitrió</translation>
<translation id="2366718077645204424">No es pot connectar amb l'amfitrió. Probablement, això passa a causa de la configuració de la xarxa que esteu fent servir.</translation>
@@ -189,7 +194,6 @@ remot de Chrome</translation>
<translation id="5308380583665731573">Connecta</translation>
<translation id="7319983568955948908">Deixa de compartir</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Tots els drets reservats.</translation>
-<translation id="8038108135592087998">Primera versió de l'escriptori remot de Chrome per a Android</translation>
<translation id="6668065415969892472">S'ha actualitzat el vostre PIN.</translation>
<translation id="4513946894732546136">Comentaris</translation>
<translation id="4277736576214464567">El codi d'accés no és vàlid. Torneu-ho a provar.</translation>
@@ -214,5 +218,6 @@ remot de Chrome</translation>
<translation id="1818475040640568770">No teniu cap ordinador registrat.</translation>
<translation id="4394049700291259645">Desactiva</translation>
<translation id="4156740505453712750">Per protegir l'accés a aquest ordinador, trieu un PIN de <ph name="BOLD_START"/>sis dígits com a mínim<ph name="BOLD_END"/>. Necessitareu aquest PIN per connectar-vos a aquest ordinador des d'una altra ubicació.</translation>
+<translation id="6398765197997659313">Surt del mode de pantalla completa</translation>
<translation id="3286521253923406898">Controlador de Chromoting Host</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_cs.xtb b/remoting/resources/remoting_strings_cs.xtb
index 577a6eb658..21463c09c6 100644
--- a/remoting/resources/remoting_strings_cs.xtb
+++ b/remoting/resources/remoting_strings_cs.xtb
@@ -14,10 +14,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Byla zjiÅ¡tÄ›na nekompatibilní verze Vzdálené plochy Chrome. Zkontrolujte, zda máte v obou poÄítaÄích nejnovÄ›jší verzi prohlížeÄe Google Chrome a Vzdálené plochy Chrome, a zkuste to znovu.</translation>
<translation id="6998989275928107238">Hostitel</translation>
<translation id="406849768426631008">Chcete někomu pomáhat a zároveň s nimi komunikovat pomocí videochatu? Zkuste funkci <ph name="LINK_BEGIN"/> Vzdálená plocha ve službě Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximalizovat okno</translation>
<translation id="5397086374758643919">Nástroj k odinstalaci hostitele Vzdálené plochy Chrome</translation>
<translation id="3258789396564295715">K tomuto poÄítaÄi se můžete bezpeÄnÄ› pÅ™ipojit pomocí Vzdálené plochy Chrome.</translation>
<translation id="5070121137485264635">Vzdálený hostitelský server vyžaduje, abyste provedli ověření na stránce tÅ™etí strany. Aby bylo možné pokraÄovat, je nutné udÄ›lit Vzdálené ploÅ¡e Chrome dodateÄná oprávnÄ›ní k přístupu na tuto adresu:</translation>
<translation id="2124408767156847088">BezpeÄnÄ› ze svého zařízení Android pÅ™istupujte k poÄítaÄům.</translation>
+<translation id="3194245623920924351">Vzdálená plocha Chrome</translation>
+<translation id="3649256019230929621">Minimalizovat okno</translation>
<translation id="4808503597364150972">Zadejte kód PIN pro poÄítaÄ <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Vzdálená pÅ™ipojení k tomuto poÄítaÄi byla zakázána.</translation>
<translation id="8244400547700556338">Další informace</translation>
@@ -28,7 +31,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Zrušit</translation>
<translation id="7782471917492991422">Zkontrolujte prosím nastavení řízení spotÅ™eby svého poÄítaÄe a ujistÄ›te se, zda není nastaven na pÅ™echod do režimu spánku pÅ™i neÄinnosti.</translation>
<translation id="7665369617277396874">PÅ™idat úÄet</translation>
+<translation id="5925497314631808737">• implementováno zobrazení na celou obrazovku / moderní režim,
+• vylepšená ikona skrytí panelu akcí.</translation>
<translation id="2707879711568641861">NÄ›které souÄásti potÅ™ebné pro Vzdálenou plochu Chrome chybí. Zkontrolujte, zda je nainstalována nejnovÄ›jší verze, a zkuste to znovu.</translation>
+<translation id="1779766957982586368">Zavřít okno</translation>
<translation id="2499160551253595098">Pomozte nám Vzdálenou plochu Chrome vylepÅ¡it tím, že nám povolíte shromažÄovat statistiky využití a zprávy o selhání.</translation>
<translation id="7868137160098754906">Zadejte kód PIN pro vzdálený poÄítaÄ.</translation>
<translation id="677755392401385740">Pro následujícího uživatele bylo zahájeno hostování: <ph name="HOST_USERNAME"/>.</translation>
@@ -96,7 +102,7 @@ Informace ohledně ochrany soukromí naleznete v zásadách ochrany soukromí sp
<translation id="1742469581923031760">Připojování...</translation>
<translation id="8998327464021325874">Hostitelský kontroler Vzdálené plochy Chrome</translation>
<translation id="6748108480210050150">Klient</translation>
-<translation id="3950820424414687140">Přihlášení</translation>
+<translation id="3950820424414687140">Přihlaste se</translation>
<translation id="1291443878853470558">Chcete-li se k tomuto poÄítaÄi pÅ™ipojit prostÅ™ednictvím funkce Chromoting, je tÅ™eba povolit vzdálená pÅ™ipojení.</translation>
<translation id="2599300881200251572">Tato služba umožňuje příchozí připojení od klientů Vzdálené plochy Chrome.</translation>
<translation id="6091564239975589852">Odeslat kombinaci kláves</translation>
@@ -135,7 +141,6 @@ Informace ohledně ochrany soukromí naleznete v zásadách ochrany soukromí sp
<translation id="4361728918881830843">Chcete-li umožnit vzdálené pÅ™ipojení k jinému poÄítaÄi, nainstalujte do nÄ›j Vzdálenou plochu Chrome a poté kliknÄ›te na tlaÄítko <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Klient připojen: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Äeká se na pÅ™ipojení...</translation>
-<translation id="5714695932513333758">Historie připojení</translation>
<translation id="811307782653349804">PÅ™istupujte ke svému poÄítaÄi odkudkoli.</translation>
<translation id="2939145106548231838">Ověření v hostitelském poÄítaÄi</translation>
<translation id="2366718077645204424">Hostitele nelze nalézt. PravdÄ›podobnou příÄinou je aktuální konfigurace sítÄ›.</translation>
@@ -190,7 +195,6 @@ plochy Chrome</translation>
<translation id="5308380583665731573">Připojit</translation>
<translation id="7319983568955948908">UkonÄit sdílení</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Všechna práva vyhrazena.</translation>
-<translation id="8038108135592087998">První vydání aplikace Vzdálená plocha Chrome pro Android.</translation>
<translation id="6668065415969892472">Váš kód PIN byl aktualizován.</translation>
<translation id="4513946894732546136">Zpětná vazba</translation>
<translation id="4277736576214464567">Přístupový kód je neplatný. Zkuste to prosím znovu.</translation>
@@ -215,5 +219,6 @@ plochy Chrome</translation>
<translation id="1818475040640568770">Nemáte zaregistrovány žádné poÄítaÄe.</translation>
<translation id="4394049700291259645">Deaktivovat</translation>
<translation id="4156740505453712750">Pokud chcete přístup k tomuto poÄítaÄi zabezpeÄit, zvolte prosím kód PIN v délce <ph name="BOLD_START"/>alespoň Å¡esti Äíslic<ph name="BOLD_END"/>. Tento kód PIN bude vyžadován pÅ™i pÅ™ipojení z jiného místa.</translation>
+<translation id="6398765197997659313">UkonÄit režim celé obrazovky</translation>
<translation id="3286521253923406898">Hostitelský kontroler funkce Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_da.xtb b/remoting/resources/remoting_strings_da.xtb
index 6f9b60c4cb..7089f5da0a 100644
--- a/remoting/resources/remoting_strings_da.xtb
+++ b/remoting/resources/remoting_strings_da.xtb
@@ -13,10 +13,13 @@ host</translation>
<translation id="2801119484858626560">Der er registreret en inkompatibel version af Chrome Fjernskrivebord. Kontrollér, at du har den nyeste version af Chrome og Chrome Fjernskrivebord på begge computere, og prøv igen.</translation>
<translation id="6998989275928107238">Til</translation>
<translation id="406849768426631008">Vil du hjælpe en anden person, mens I videochatter? Prøv <ph name="LINK_BEGIN"/>Fjernskrivebord i Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksimér vinduet</translation>
<translation id="5397086374758643919">Afinstallationsprogram til host for Chrome Fjernskrivebord</translation>
<translation id="3258789396564295715">Du kan få sikker adgang til denne computer via Chrome Fjernskrivebord.</translation>
<translation id="5070121137485264635">Fjernværten kræver, at du bekræftes over for et tredjepartswebsite. Du skal give Chrome Fjernskrivebord yderligere tilladelser til at få adgang til denne adresse, før du kan fortsætte:</translation>
<translation id="2124408767156847088">Sikker adgang til dine computere fra din Android-enhed.</translation>
+<translation id="3194245623920924351">Chrome Fjernskrivebord</translation>
+<translation id="3649256019230929621">Minimer vinduet</translation>
<translation id="4808503597364150972">Indtast din pinkode til <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Fjernforbindelserne til denne computer er blevet deaktiveret.</translation>
<translation id="8244400547700556338">FÃ¥ flere oplysninger.</translation>
@@ -27,7 +30,10 @@ host</translation>
<translation id="7658239707568436148">Annuller</translation>
<translation id="7782471917492991422">Kontrollér indstillingerne for strømstyring på din computer, og sørg for, at den ikke er konfigureret til at gå i dvale, når den er inaktiv.</translation>
<translation id="7665369617277396874">Tilføj konto</translation>
+<translation id="5925497314631808737">• Fuld skærm/fordybningstilstand er implementeret.
+• Ikon til at skjule handlingsbjælken er forbedret.</translation>
<translation id="2707879711568641861">Nogle komponenter, der kræves til Chrome Fjernskrivebord, mangler. Kontrollér, at du har den nyeste version af Google Chrome, og prøv igen.</translation>
+<translation id="1779766957982586368">Luk vindue</translation>
<translation id="2499160551253595098">Hjælp os med at forbedre Chrome Fjernskrivebord ved at lade os indsamle brugsstatistikker og rapporter om nedbrud.</translation>
<translation id="7868137160098754906">Indtast din pinkode til fjerncomputeren.</translation>
<translation id="677755392401385740">Host er startet for bruger: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Du kan få flere oplysninger om privatliv i privatlivspolitikkerne for Google (h
<translation id="4361728918881830843">Hvis du vil aktivere fjernforbindelser til en anden computer, skal du installere Chrome Fjernskrivebord på den pågældende computer og klikke på &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Tilsluttet klient: <ph name="CLIENT_USERNAME"/> .</translation>
<translation id="4913529628896049296">venter på forbindelse...</translation>
-<translation id="5714695932513333758">Forbindelseshistorik</translation>
<translation id="811307782653349804">FÃ¥ adgang til din egen computer, uanset hvor du er.</translation>
<translation id="2939145106548231838">Godkend over for host</translation>
<translation id="2366718077645204424">Værten kunne ikke nås. Dette skyldes sandsynligvis konfigurationen af det netværk, du bruger.</translation>
@@ -189,7 +194,6 @@ Fjernskrivebord</translation>
<translation id="5308380583665731573">Opret forbindelse</translation>
<translation id="7319983568955948908">Stop deling</translation>
<translation id="6681800064886881394">Copyright © 2013 Google Inc. Alle rettigheder forbeholdes.</translation>
-<translation id="8038108135592087998">Første udgivelse af Chrome Fjernskrivebord til Android.</translation>
<translation id="6668065415969892472">Din pinkode er blevet opdateret.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">Adgangskoden er ugyldig. Prøv igen.</translation>
@@ -214,5 +218,6 @@ Fjernskrivebord</translation>
<translation id="1818475040640568770">Du har ingen registrerede computere.</translation>
<translation id="4394049700291259645">Deaktiver</translation>
<translation id="4156740505453712750">Beskyt adgangen til denne computer ved at vælge en pinkode på <ph name="BOLD_START"/>mindst seks cifre<ph name="BOLD_END"/>. Denne pinkode skal indtastes, når du opretter forbindelse fra et andet sted.</translation>
+<translation id="6398765197997659313">Afslut fuld skærm</translation>
<translation id="3286521253923406898">Administration af Chromoting-host</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_de.xtb b/remoting/resources/remoting_strings_de.xtb
index 1f7bb4e305..dafc1f2a5a 100644
--- a/remoting/resources/remoting_strings_de.xtb
+++ b/remoting/resources/remoting_strings_de.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">Eine inkompatible Version von Chrome Remote Desktop wurde erkannt. Überprüfen Sie, ob die neueste Version von Chrome und Chrome Remote Desktop auf beiden Computern installiert ist, und versuchen Sie es erneut.</translation>
<translation id="6998989275928107238">Zu</translation>
<translation id="406849768426631008">Möchten Sie jemandem helfen und gleichzeitig per Video mit ihm chatten? Testen Sie <ph name="LINK_BEGIN"/>Remote Desktop in Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Fenster maximieren</translation>
<translation id="5397086374758643919">Deinstallationsprogramm für Chrome Remote Desktop Host</translation>
<translation id="3258789396564295715">Mit Chrome Remote Desktop erhalten Sie sicheren Zugriff auf diesen Computer.</translation>
<translation id="5070121137485264635">Der Remote-Host verlangt, dass Sie sich über eine Drittanbieter-Website authentifizieren. Um fortfahren zu können, geben Sie Chrome Remote Desktop zusätzliche Berechtigungen für den Zugriff auf die folgende Adresse:</translation>
<translation id="2124408767156847088">Für den sicheren Zugriff von Ihrem Android-Gerät auf Ihre Computer</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Fenster minimieren</translation>
<translation id="4808503597364150972">Bitte geben Sie Ihre PIN für <ph name="HOSTNAME"/> ein.</translation>
<translation id="7672203038394118626">Die Remote-Verbindungen für diesen Computer wurden deaktiviert.</translation>
<translation id="8244400547700556338">Weitere Informationen</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Abbrechen</translation>
<translation id="7782471917492991422">Bitte überprüfen Sie die Energieverwaltungseinstellungen Ihres Computers. Vergewissern Sie sich, dass er nicht so konfiguriert ist, dass er bei Inaktivität in den Energiesparmodus schaltet.</translation>
<translation id="7665369617277396874">Konto hinzufügen</translation>
+<translation id="5925497314631808737">• Vollbild-/immersiver Modus implementiert
+• Symbol zum Ausblenden der Aktionsleiste optimiert</translation>
<translation id="2707879711568641861">Es fehlen einige Komponenten, die für Chrome Remote Desktop erforderlich sind. Überprüfen Sie, ob die neueste Version installiert ist, und versuchen Sie es erneut.</translation>
+<translation id="1779766957982586368">Fenster schließen</translation>
<translation id="2499160551253595098">Helfen Sie uns bei der Verbesserung von Chrome Remote Desktop, indem Sie zulassen, dass wir Nutzungsstatistiken und Absturzberichte erfassen.</translation>
<translation id="7868137160098754906">Geben Sie Ihre PIN für den Remote-Computer ein.</translation>
<translation id="677755392401385740">Host für folgenden Nutzer gestartet: <ph name="HOST_USERNAME"/></translation>
@@ -134,7 +140,6 @@ Informationen zum Datenschutz finden Sie in der Google-Datenschutzerklärung (ht
<translation id="4361728918881830843">Um Remote-Verbindungen zu einem anderen Computer zu ermöglichen, installieren Sie Chrome Remote Desktop auf dem betreffenden Computer und klicken Sie auf &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Client verbunden: <ph name="CLIENT_USERNAME"/></translation>
<translation id="4913529628896049296">Warten auf Verbindung...</translation>
-<translation id="5714695932513333758">Verlauf &quot;Verbindungen&quot;</translation>
<translation id="811307782653349804">Greifen Sie von überall aus auf Ihren Computer zu.</translation>
<translation id="2939145106548231838">Authentifizierung gegenüber dem Host</translation>
<translation id="2366718077645204424">Die Verbindung zum Host kann nicht hergestellt werden. Das von Ihnen verwendete Netzwerk ist möglicherweise nicht korrekt konfiguriert.</translation>
@@ -189,7 +194,6 @@ Desktop Host</translation>
<translation id="5308380583665731573">Verbinden</translation>
<translation id="7319983568955948908">Freigabe beenden</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Alle Rechte vorbehalten</translation>
-<translation id="8038108135592087998">Erste Version von Chrome Remote Desktop für Android</translation>
<translation id="6668065415969892472">Ihre PIN wurde aktualisiert.</translation>
<translation id="4513946894732546136">Google Feedback</translation>
<translation id="4277736576214464567">Der Zugriffscode ist ungültig. Bitte versuchen Sie es erneut.</translation>
@@ -214,5 +218,6 @@ Desktop Host</translation>
<translation id="1818475040640568770">Es sind keine registrierten Computer vorhanden.</translation>
<translation id="4394049700291259645">Deaktivieren</translation>
<translation id="4156740505453712750">Um den Zugriff auf diesen Computer zu schützen, wählen Sie eine PIN mit <ph name="BOLD_START"/>mindestens 6 Ziffern<ph name="BOLD_END"/> aus. Die Eingabe dieser PIN ist erforderlich, wenn eine Verbindung von einem anderen Ort aus hergestellt wird.</translation>
+<translation id="6398765197997659313">Vollbildmodus beenden</translation>
<translation id="3286521253923406898">Chromoting Host-Controller</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_el.xtb b/remoting/resources/remoting_strings_el.xtb
index 16a5ecf769..91a078a7d3 100644
--- a/remoting/resources/remoting_strings_el.xtb
+++ b/remoting/resources/remoting_strings_el.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Εντοπίστηκε μια μη συμβατή έκδοση της ΑπομακÏυσμένης επιφάνειας εÏγασίας Chrome. Βεβαιωθείτε ότι έχετε εγκαταστήσει την πιο Ï€Ïόσφατη έκδοση του Chrome καθώς και την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome και στου δÏο υπολογιστές και δοκιμάστε ξανά.</translation>
<translation id="6998989275928107238">ΠÏος</translation>
<translation id="406849768426631008">Θέλετε να βοηθήσετε κάποιον, ενώ παÏάλληλα συνομιλείτε μαζί του μέσω βίντεο; Δοκιμάστε την <ph name="LINK_BEGIN"/>ΑπομακÏυσμένη επιφάνεια εÏγασίας στο Google Hangouts<ph name="LINK_END"/> .</translation>
+<translation id="906458777597946297">Μεγιστοποίηση παÏαθÏÏου</translation>
<translation id="5397086374758643919">ΠÏόγÏαμμα απεγκατάστασης κεντÏÎ¹ÎºÎ¿Ï Ï…Ï€Î¿Î»Î¿Î³Î¹ÏƒÏ„Î® ΑπομακÏυσμένης επιφάνειας εÏγασίας Chrome</translation>
<translation id="3258789396564295715">ΜποÏείτε να αποκτήσετε ασφαλή Ï€Ïόσβαση σε αυτόν τον υπολογιστή χÏησιμοποιώντας την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome.</translation>
<translation id="5070121137485264635">Ο απομακÏυσμένος κεντÏικός υπολογιστής ζήτησε τον έλεγχο της ταυτότητάς σας στον ιστότοπο ενός Ï„Ïίτου μέÏους. Για να συνεχίσετε, θα Ï€Ïέπει να παÏαχωÏήσετε στην ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome επιπλέον άδειες για Ï€Ïόσβαση σε αυτήν τη διεÏθυνση:</translation>
<translation id="2124408767156847088">Αποκτήστε ασφαλή Ï€Ïόσβαση στους υπολογιστές σας από συσκευή Android.</translation>
+<translation id="3194245623920924351">ΑπομακÏ. επιφάνεια εÏγασίας</translation>
+<translation id="3649256019230929621">Ελαχιστοποίηση παÏαθÏÏου</translation>
<translation id="4808503597364150972">Εισαγάγετε το PIN σας για τον κεντÏικό υπολογιστή <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Έχουν απενεÏγοποιηθεί οι απομακÏυσμένες συνδέσεις για αυτόν τον υπολογιστή.</translation>
<translation id="8244400547700556338">Μάθετε πώς.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">ΑκÏÏωση</translation>
<translation id="7782471917492991422">Ελέγξτε τις Ïυθμίσεις διαχείÏισης ενέÏγειας του υπολογιστή σας και βεβαιωθείτε ότι δεν έχει διαμοÏφωθεί να τίθεται σε αναστολή λειτουÏγίας όταν είναι αδÏανής.</translation>
<translation id="7665369617277396874">ΠÏοσθήκη λογαÏιασμοÏ</translation>
+<translation id="5925497314631808737">• ΕφαÏμοσμένη λειτουÏγία πλήÏους οθόνης / λειτουÏγία εμβυθιστικής Ï€Ïοβολής.
+• Βελτιωμένο εικονίδιο για την απόκÏυψη της γÏαμμής ενεÏγειών.</translation>
<translation id="2707879711568641861">Λείπουν οÏισμένα στοιχεία που απαιτοÏνται για την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome. Βεβαιωθείτε ότι έχετε εγκαταστήσει την πιο Ï€Ïόσφατη έκδοση και δοκιμάστε ξανά.</translation>
+<translation id="1779766957982586368">Κλείσιμο παÏαθÏÏου</translation>
<translation id="2499160551253595098">Βοηθήστε μας να βελτιώσουμε την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome επιτÏέποντάς μας να συγκεντÏώσουμε στατιστικά στοιχεία χÏήσης και αναφοÏές σφαλμάτων.</translation>
<translation id="7868137160098754906">ΚαταχωÏίστε το PIN σας για τον απομακÏυσμένο υπολογιστή.</translation>
<translation id="677755392401385740">Ο κεντÏικός υπολογιστής ξεκίνησε για το χÏήστη: <ph name="HOST_USERNAME"/>.</translation>
@@ -46,7 +52,7 @@ Chromoting</translation>
<translation id="5064360042339518108"><ph name="HOSTNAME"/> (εκτός σÏνδεσης)</translation>
<translation id="4472575034687746823">ΈναÏξη</translation>
<translation id="2813770873348017932">Έχει γίνει Ï€ÏοσωÏινός αποκλεισμός των συνδέσεων στον απομακÏυσμένο υπολογιστή επειδή κάποιος επιχείÏησε να συνδεθεί σε αυτόν με μη έγκυÏο PIN. ΠÏοσπαθήστε ξανά αÏγότεÏα.</translation>
-<translation id="6746493157771801606">ΕκκαθάÏιση ιστοÏικοÏ</translation>
+<translation id="6746493157771801606">ΔιαγÏαφή ιστοÏικοÏ</translation>
<translation id="4430435636878359009">ΑπενεÏγοποίηση απομακÏυσμένων συνδέσεων σε αυτόν τον υπολογιστή</translation>
<translation id="6001953797859482435">ΠÏοτιμήσεις κεντÏÎ¹ÎºÎ¿Ï Ï…Ï€Î¿Î»Î¿Î³Î¹ÏƒÏ„Î® ΑπομακÏυσμένης επιφάνειας εÏγασίας Chrome</translation>
<translation id="9188433529406846933">Εξουσιοδότηση</translation>
@@ -134,7 +140,6 @@ Chromoting</translation>
<translation id="4361728918881830843">Για να ενεÏγοποιήσετε τις απομακÏυσμένες συνδέσεις σε έναν διαφοÏετικό υπολογιστή, εγκαταστήστε την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome σε αυτόν και κάντε κλικ στο κουμπί &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Συνδεδεμένος υπολογιστής-πελάτης: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">αναμονή για σÏνδεση…</translation>
-<translation id="5714695932513333758">ΙστοÏικό συνδέσεων</translation>
<translation id="811307782653349804">Αποκτήστε Ï€Ïόσβαση στον υπολογιστή σας από οπουδήποτε.</translation>
<translation id="2939145106548231838">Έλεγχος ταυτότητας στον κεντÏικό υπολογιστή</translation>
<translation id="2366718077645204424">Δεν ήταν δυνατή η επικοινωνία με τον κεντÏικό υπολογιστή. Αυτό ενδέχεται να οφείλεται στη διαμόÏφωση του δικτÏου που χÏησιμοποιείτε.</translation>
@@ -160,7 +165,7 @@ Chromoting</translation>
<translation id="4006787130661126000">Θα Ï€Ïέπει να ενεÏγοποιήσετε τις απομακÏυσμένες συνδέσεις εάν επιθυμείτε να χÏησιμοποιήσετε την ΑπομακÏυσμένη επιφάνεια εÏγασίας Chrome για Ï€Ïόσβαση σε αυτόν τον υπολογιστή.</translation>
<translation id="177096447311351977">ΔιεÏθυνση IP ÎºÎ±Î½Î±Î»Î¹Î¿Ï Î³Î¹Î± υπολογιστή-πελάτη: <ph name="CLIENT_GAIA_IDENTIFIER"/> ip='<ph name="CLIENT_IP_ADDRESS_AND_PORT"/>' host_ip='<ph name="HOST_IP_ADDRESS_AND_PORT"/>' channel='<ph name="CHANNEL_TYPE"/>' connection='<ph name="CONNECTION_TYPE"/>'.</translation>
<translation id="6930242544192836755">ΔιάÏκεια</translation>
-<translation id="6527303717912515753">Κοινοποίηση</translation>
+<translation id="6527303717912515753">Κοινή χÏήση</translation>
<translation id="2926340305933667314">Αποτυχία απενεÏγοποίησης απομακÏυσμένης Ï€Ïόσβασης σε αυτόν τον υπολογιστή. ΠÏοσπαθήστε ξανά αÏγότεÏα.</translation>
<translation id="6865175692670882333">ΠÏοβολή/επεξεÏγασία</translation>
<translation id="5859141382851488196">Îέο παÏάθυÏο…</translation>
@@ -189,7 +194,6 @@ Chromoting</translation>
<translation id="5308380583665731573">ΣÏνδεση</translation>
<translation id="7319983568955948908">Διακοπή κοινής χÏήσης</translation>
<translation id="6681800064886881394">Πνευματικά δικαιώματα 2013 Google Inc. Με την επιφÏλαξη παντός δικαιώματος.</translation>
-<translation id="8038108135592087998">ΠÏώτη έκδοση της ΑπομακÏυσμένης επιφάνειας εÏγασίας Chrome για Android.</translation>
<translation id="6668065415969892472">Το PIN σας έχει ενημεÏωθεί.</translation>
<translation id="4513946894732546136">Σχόλια</translation>
<translation id="4277736576214464567">Ο κωδικός Ï€Ïόσβασης δεν είναι έγκυÏος. Δοκιμάστε ξανά.</translation>
@@ -214,5 +218,6 @@ Chromoting</translation>
<translation id="1818475040640568770">Δεν έχετε εγγεγÏαμμένους υπολογιστές.</translation>
<translation id="4394049700291259645">ΑπενεÏγοποίηση</translation>
<translation id="4156740505453712750">Για να Ï€ÏοστατεÏσετε την Ï€Ïόσβαση σε αυτόν τον υπολογιστή, επιλέξτε ένα PIN το οποίο αποτελείται από <ph name="BOLD_START"/>τουλάχιστον έξι ψηφία<ph name="BOLD_END"/>. Αυτό το PIN θα ζητείται όταν συνδέεστε από άλλη τοποθεσία.</translation>
+<translation id="6398765197997659313">Έξοδος από πλήÏη οθόνη</translation>
<translation id="3286521253923406898">ΧειÏιστήÏιο κεντÏÎ¹ÎºÎ¿Ï Ï…Ï€Î¿Î»Î¿Î³Î¹ÏƒÏ„Î® Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_en-GB.xtb b/remoting/resources/remoting_strings_en-GB.xtb
index 6f4af27224..1edc462a39 100644
--- a/remoting/resources/remoting_strings_en-GB.xtb
+++ b/remoting/resources/remoting_strings_en-GB.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">An incompatible version of Chrome Remote Desktop was detected. Please make sure that you have the latest version of Chrome and Chrome Remote Desktop on both computers and try again.</translation>
<translation id="6998989275928107238">To</translation>
<translation id="406849768426631008">Want to help someone while having a video chat with them too? Try <ph name="LINK_BEGIN"/> Remote Desktop in Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximise window</translation>
<translation id="5397086374758643919">Chrome Remote Desktop Host Uninstaller</translation>
<translation id="3258789396564295715">You may securely access this computer using Chrome Remote Desktop.</translation>
<translation id="5070121137485264635">The remote host requires you to authenticate to a third-party website. To continue, you must grant Chrome Remote Desktop additional permissions to access this address:</translation>
<translation id="2124408767156847088">Securely access your computers from your Android device.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Minimise window</translation>
<translation id="4808503597364150972">Please enter your PIN for <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Remote connections for this computer have been disabled.</translation>
<translation id="8244400547700556338">Learn how.</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Cancel</translation>
<translation id="7782471917492991422">Please check your computer's power management settings and ensure that it is not configured to sleep when idle.</translation>
<translation id="7665369617277396874">Add account</translation>
+<translation id="5925497314631808737">• Implemented full-screen / immersive mode.
+• Improved icon for hiding the action bar.</translation>
<translation id="2707879711568641861">Some components required for Chrome Remote Desktop are missing. Please make sure you have installed the latest version and try again.</translation>
+<translation id="1779766957982586368">Close window</translation>
<translation id="2499160551253595098">Help us to improve Chrome Remote Desktop by allowing us to collect usage statistics and crash reports.</translation>
<translation id="7868137160098754906">Please enter your PIN for the remote computer.</translation>
<translation id="677755392401385740">Host started for user: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ For information about privacy, please see the Google Privacy Policy (http://goo.
<translation id="4361728918881830843">To enable remote connections to a different computer, install Chrome Remote Desktop there and click “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Client connected: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">waiting for connection…</translation>
-<translation id="5714695932513333758">Connection history</translation>
<translation id="811307782653349804">Access your own computer from anywhere.</translation>
<translation id="2939145106548231838">Authenticate to host</translation>
<translation id="2366718077645204424">Unable to reach the host. This is probably due to the configuration of the network you are using.</translation>
@@ -189,7 +194,6 @@ Desktop Host</translation>
<translation id="5308380583665731573">Connect</translation>
<translation id="7319983568955948908">Stop Sharing</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. All Rights Reserved.</translation>
-<translation id="8038108135592087998">First release of Chrome Remote Desktop for Android.</translation>
<translation id="6668065415969892472">Your PIN has been updated.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">The access code is invalid. Please try again.</translation>
@@ -214,5 +218,6 @@ Desktop Host</translation>
<translation id="1818475040640568770">You have no computers registered.</translation>
<translation id="4394049700291259645">Disable</translation>
<translation id="4156740505453712750">To protect access to this computer, please choose a PIN of <ph name="BOLD_START"/>at least six digits<ph name="BOLD_END"/>. This PIN will be required when connecting from another location.</translation>
+<translation id="6398765197997659313">Exit full screen</translation>
<translation id="3286521253923406898">Chromoting Host Controller</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_es-419.xtb b/remoting/resources/remoting_strings_es-419.xtb
index f2b32cd033..2556651b17 100644
--- a/remoting/resources/remoting_strings_es-419.xtb
+++ b/remoting/resources/remoting_strings_es-419.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Se detectó una versión incompatible del Escritorio remoto de Chrome. Asegúrate de que se hayan instalado la última versión de Chrome y del Escritorio remoto de Chrome en ambas computadoras y vuelve a intentarlo.</translation>
<translation id="6998989275928107238">Para</translation>
<translation id="406849768426631008">¿Quieres ayudar a otra persona al mismo tiempo que mantienes con ella un videochat? Prueba el <ph name="LINK_BEGIN"/>Escritorio remoto en Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximizar ventana</translation>
<translation id="5397086374758643919">Desinstalador del host del Escritorio remoto de Chrome</translation>
<translation id="3258789396564295715">Puedes acceder a esta computadora sin correr riesgos a través del Escritorio remoto de Chrome.</translation>
<translation id="5070121137485264635">El host remoto requiere la autenticación de un sitio web de terceros. Para continuar, debes otorgar permisos adicionales al Escritorio remoto de Chrome para acceder a la siguiente dirección:</translation>
<translation id="2124408767156847088">Acceder de forma segura a tus computadoras desde el dispositivo Android</translation>
+<translation id="3194245623920924351">Escritorio remoto de Chrome</translation>
+<translation id="3649256019230929621">Minimizar ventana</translation>
<translation id="4808503597364150972">Ingresa tu PIN de <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Se inhabilitaron las conexiones remotas con esta computadora.</translation>
<translation id="8244400547700556338">Más información</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Cancelar</translation>
<translation id="7782471917492991422">Configura la administración de energía de tu computadora y asegúrate de que no se habilite el modo de hibernación cuando esté inactiva.</translation>
<translation id="7665369617277396874">Agregar cuenta</translation>
+<translation id="5925497314631808737">• Se implementaron el modo de pantalla completa y el modo envolvente.
+• Se mejoró el ícono para ocultar la barra de acciones.</translation>
<translation id="2707879711568641861">Faltan algunos de los componentes necesarios para Escritorio remoto de Chrome. Asegúrate de haber instalado la última versión y vuelve a intentarlo.</translation>
+<translation id="1779766957982586368">Cerrar ventana</translation>
<translation id="2499160551253595098">Ayúdanos a mejorar la aplicación Escritorio remoto de Chrome permitiéndonos recopilar estadísticas de uso e informes sobre fallos.</translation>
<translation id="7868137160098754906">Ingresa tu PIN para la computadora remota.</translation>
<translation id="677755392401385740">Host iniciado para usuario: <ph name="HOST_USERNAME"/></translation>
@@ -134,7 +140,6 @@ Para obtener más información sobre la privacidad, consulta la política de pri
<translation id="4361728918881830843">Para habilitar conexiones remotas con otra computadora, instala allí Escritorio remoto de Chrome y haz clic en “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Cliente conectado: <ph name="CLIENT_USERNAME"/></translation>
<translation id="4913529628896049296">Aguardando la conexión...</translation>
-<translation id="5714695932513333758">Historial de conexiones</translation>
<translation id="811307782653349804">Accede a tu computadora desde cualquier lugar.</translation>
<translation id="2939145106548231838">Autenticarse en host</translation>
<translation id="2366718077645204424">No se puede establecer la conexión con el host. Probablemente esto se deba a la configuración de la red que usas.</translation>
@@ -189,7 +194,6 @@ remoto de Chrome</translation>
<translation id="5308380583665731573">Conectar</translation>
<translation id="7319983568955948908">Dejar de compartir</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Todos los derechos reservados.</translation>
-<translation id="8038108135592087998">Primera versión de Escritorio remoto de Chrome para Android</translation>
<translation id="6668065415969892472">Se actualizó tu PIN.</translation>
<translation id="4513946894732546136">Comentario</translation>
<translation id="4277736576214464567">El código de acceso no es válido. Vuelve a intentarlo.</translation>
@@ -214,5 +218,6 @@ remoto de Chrome</translation>
<translation id="1818475040640568770">No hay computadoras registradas.</translation>
<translation id="4394049700291259645">Inhabilitar</translation>
<translation id="4156740505453712750">Debes elegir un PIN de <ph name="BOLD_START"/>al menos seis dígitos<ph name="BOLD_END"/> para proteger el acceso a esta computadora. Se solicitará ese PIN cada vez que se establezca una conexión con esa computadora desde otra ubicación.</translation>
+<translation id="6398765197997659313">Salir de pantalla completa</translation>
<translation id="3286521253923406898">Controlador del host de Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_es.xtb b/remoting/resources/remoting_strings_es.xtb
index df70c167bb..db1855bc62 100644
--- a/remoting/resources/remoting_strings_es.xtb
+++ b/remoting/resources/remoting_strings_es.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Hemos detectado que tu versión de Escritorio remoto de Chrome no es compatible. Asegúrate de haber instalado en ambos ordenadores la última versión de Chrome y de Escritorio remoto de Chrome.</translation>
<translation id="6998989275928107238">Para</translation>
<translation id="406849768426631008">¿Quieres ayudar a otra persona mientras mantienes con ella un chat de vídeo? Prueba <ph name="LINK_BEGIN"/>Escritorio remoto en Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximizar ventana</translation>
<translation id="5397086374758643919">Programa de desinstalación del host de Escritorio remoto de Chrome</translation>
<translation id="3258789396564295715">Puedes acceder de forma segura a este ordenador con Escritorio remoto de Chrome.</translation>
<translation id="5070121137485264635">El host remoto requiere tu autenticación en un sitio web de terceros. Para continuar, debes conceder a la aplicación Escritorio remoto de Chrome permisos adicionales para acceder a la siguiente dirección:</translation>
<translation id="2124408767156847088">Accede de forma segura a tus ordenadores desde tu dispositivo Android.</translation>
+<translation id="3194245623920924351">Escritorio remoto de Chrome</translation>
+<translation id="3649256019230929621">Minimizar ventana</translation>
<translation id="4808503597364150972">Introduce el PIN de <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Se han inhabilitado las conexiones remotas con este ordenador.</translation>
<translation id="8244400547700556338">Más información</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Cancelar</translation>
<translation id="7782471917492991422">Comprueba las opciones de energía de tu ordenador y asegúrate de que no esté configurado para entrar en modo de suspensión cuando esté inactivo.</translation>
<translation id="7665369617277396874">Añadir cuenta</translation>
+<translation id="5925497314631808737">• Modo de pantalla completa/envolvente implementado.
+• Icono mejorado para ocultar la barra de acciones.</translation>
<translation id="2707879711568641861">Faltan algunos de los componentes necesarios para Escritorio remoto de Chrome. Asegúrate de haber instalado la última versión y vuelve a intentarlo.</translation>
+<translation id="1779766957982586368">Cerrar ventana</translation>
<translation id="2499160551253595098">Ayúdanos a mejorar la aplicación Escritorio remoto de Chrome autorizando la recopilación de estadísticas de uso e informes sobre fallos.</translation>
<translation id="7868137160098754906">Introduce tu código PIN para el ordenador remoto.</translation>
<translation id="677755392401385740">Host iniciado para usuario: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Para obtener información sobre la privacidad, consulta la Política de privacid
<translation id="4361728918881830843">Para habilitar conexiones remotas con otro ordenador, instala Escritorio remoto de Chrome en él y haz clic en <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Cliente conectado: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">esperando conexión...</translation>
-<translation id="5714695932513333758">Historial de conexiones</translation>
<translation id="811307782653349804">Posibilidad de acceso a tu ordenador desde cualquier lugar</translation>
<translation id="2939145106548231838">Autenticarse en host</translation>
<translation id="2366718077645204424">No se puede establecer conexión con el host. Es probable que el error se deba a la configuración de la red que se está utilizando.</translation>
@@ -189,7 +194,6 @@ remoto de Chrome</translation>
<translation id="5308380583665731573">Conectar</translation>
<translation id="7319983568955948908">Dejar de compartir</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Todos los derechos reservados.</translation>
-<translation id="8038108135592087998">Primera versión de Escritorio remoto de Chrome para Android.</translation>
<translation id="6668065415969892472">Se ha actualizado el PIN.</translation>
<translation id="4513946894732546136">Comentarios</translation>
<translation id="4277736576214464567">El código de acceso no es válido. Vuelve a intentarlo.</translation>
@@ -214,5 +218,6 @@ remoto de Chrome</translation>
<translation id="1818475040640568770">No tienes ordenadores registrados.</translation>
<translation id="4394049700291259645">Inhabilitar</translation>
<translation id="4156740505453712750">Debes elegir un PIN de <ph name="BOLD_START"/>al menos seis dígitos<ph name="BOLD_END"/> para proteger el acceso a este ordenador. Se solicitará ese PIN cada vez que se establezca conexión con ese ordenador desde otra ubicación.</translation>
+<translation id="6398765197997659313">Salir del modo de pantalla completa</translation>
<translation id="3286521253923406898">Controlador de host de Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_et.xtb b/remoting/resources/remoting_strings_et.xtb
index 0d4ce7c1a7..c83b4839db 100644
--- a/remoting/resources/remoting_strings_et.xtb
+++ b/remoting/resources/remoting_strings_et.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">Tuvastati Chrome Remote Desktopi ühildumatu verisoon. Veenduge, et mõlemasse arvutisse on installitud Google Chrome'i ja Chrome Remote Desktopi uusim versioon, ning proovige uuesti.</translation>
<translation id="6998989275928107238">Host</translation>
<translation id="406849768426631008">Kas soovite kedagi aidata, pidades temaga samal ajal ka videovestlust? Proovige <ph name="LINK_BEGIN"/> Google Hangoutsi kaugtöölauafunktsiooni<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksimeeri akent</translation>
<translation id="5397086374758643919">Chrome Remote Desktopi hosti desinstaller</translation>
<translation id="3258789396564295715">Pääsete turvaliselt arvuti juurde Chrome Remote Desktopi abil.</translation>
<translation id="5070121137485264635">Kaughost vajab autentimist kolmanda osapoole veebisaidil. Jätkamiseks peate andma Chrome Remote Desktopile täiendavad load juurdepääsuks sellele aadressile:</translation>
<translation id="2124408767156847088">Hankige oma Android-seadmest turvaliselt juurdepääs oma arvutitele.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Minimeeri aken</translation>
<translation id="4808503597364150972">Sisestage hosti <ph name="HOSTNAME"/> PIN-kood.</translation>
<translation id="7672203038394118626">Selles arvutis on kaugühendused keelatud.</translation>
<translation id="8244400547700556338">Lisateave.</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Tühista</translation>
<translation id="7782471917492991422">Kontrollige oma arvuti toitehalduse seadeid ja veenduge, et arvuti ei ole seadistatud lülituma jõudeolekul unerežiimile.</translation>
<translation id="7665369617277396874">Konto lisamine</translation>
+<translation id="5925497314631808737">• Rakendatakse täisekraani / immersiivset režiimi.
+• Täiustatud ikoon toiminguriba peitmiseks.</translation>
<translation id="2707879711568641861">Mõned Chrome Remote Desktopi jaoks vajalikud komponendid on puudu. Veenduge, et installitud on uusim versioon, ja proovige uuesti.</translation>
+<translation id="1779766957982586368">Sulgeb akna</translation>
<translation id="2499160551253595098">Aidake meil Chrome Remote Desktopi täiustada, lubades meil koguda kasutusstatistikat ja krahhiaruandeid.</translation>
<translation id="7868137160098754906">Sisestage kaugarvuti PIN-kood.</translation>
<translation id="677755392401385740">Kasutajale käivitati host: <ph name="HOST_USERNAME"/>.</translation>
@@ -95,7 +101,7 @@ Lisateavet privaatsuse kohta vaadake Google'i privaatsuseeskirjadest (http://goo
<translation id="1742469581923031760">Ãœhendamine ...</translation>
<translation id="8998327464021325874">Chrome Remote Desktopi hosti kontroller</translation>
<translation id="6748108480210050150">Allikas</translation>
-<translation id="3950820424414687140">Logi sisse</translation>
+<translation id="3950820424414687140">Sisselogimine</translation>
<translation id="1291443878853470558">Kui soovite rakendusega Chromoting arvuti juurde pääseda, siis lubage kaugühendused.</translation>
<translation id="2599300881200251572">See teenus lubab sissetulevad ühendused Chrome Remote Desktopi klientidelt.</translation>
<translation id="6091564239975589852">Klahvide saatmine</translation>
@@ -134,7 +140,6 @@ Lisateavet privaatsuse kohta vaadake Google'i privaatsuseeskirjadest (http://goo
<translation id="4361728918881830843">Muu arvutiga kaugühenduse lubamiseks installige sinna Chrome Remote Desktop ja klõpsake nupul „<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Ãœhendatud klient: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">ühenduse ootamine ...</translation>
-<translation id="5714695932513333758">Ãœhenduse ajalugu</translation>
<translation id="811307782653349804">Kasutage oma arvutit ükskõik kus.</translation>
<translation id="2939145106548231838">Hostiga autentimine</translation>
<translation id="2366718077645204424">Hostiga ei saa ühendust. Seda põhjustab ilmselt kasutatava võrgu konfiguratsioon.</translation>
@@ -189,7 +194,6 @@ Desktopi host</translation>
<translation id="5308380583665731573">Ãœhenda</translation>
<translation id="7319983568955948908">Lõpeta jagamine</translation>
<translation id="6681800064886881394">Autoriõigus 2013 Google Inc. Kõik õigused kaitstud.</translation>
-<translation id="8038108135592087998">Androidi Chrome Remote Desktopi esimene väljalase.</translation>
<translation id="6668065415969892472">Teie PIN-kood on värskendatud.</translation>
<translation id="4513946894732546136">Tagasiside</translation>
<translation id="4277736576214464567">Pääsukood on kehtetu. Proovige uuesti.</translation>
@@ -214,5 +218,6 @@ Desktopi host</translation>
<translation id="1818475040640568770">Te ei ole registreerinud ühtegi arvutit.</translation>
<translation id="4394049700291259645">Keela</translation>
<translation id="4156740505453712750">Arvuti kaitsmiseks valige PIN-kood, mis koosneb <ph name="BOLD_START"/>vähemalt kuuest numbrist<ph name="BOLD_END"/>. PIN-kood tuleb sisestada muust asukohast ühenduse loomisel.</translation>
+<translation id="6398765197997659313">Välju täisekraanilt</translation>
<translation id="3286521253923406898">Chromootimise hosti kontroller</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_fi.xtb b/remoting/resources/remoting_strings_fi.xtb
index f431cbe0eb..c45f03b49e 100644
--- a/remoting/resources/remoting_strings_fi.xtb
+++ b/remoting/resources/remoting_strings_fi.xtb
@@ -13,10 +13,13 @@ isäntä</translation>
<translation id="2801119484858626560">Havaittiin yhteensopimaton Chrome-etäkäyttöversio. Varmista, että molemmissa tietokoneissa on uusimmat Chrome- ja Chrome-etäkäyttöversiot, ja yritä uudelleen.</translation>
<translation id="6998989275928107238">Kohde</translation>
<translation id="406849768426631008">Haluatko auttaa jotakuta videopuhelun kautta? Kokeile <ph name="LINK_BEGIN"/> etäkäyttöä Googlen Hangout-keskusteluissa<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Suurenna ikkuna</translation>
<translation id="5397086374758643919">Chrome-etäkäytön isännän asennuksen poisto</translation>
<translation id="3258789396564295715">Tähän tietokoneeseen voi muodostaa suojatun yhteyden Chrome-etäkäytön avulla.</translation>
<translation id="5070121137485264635">Etäisäntä edellyttää todennusta kolmannen osapuolen sivustossa. Jos haluat jatkaa, anna Chrome-etäkäytölle lisäkäyttölupia tämän osoitteen avaamiseksi:</translation>
<translation id="2124408767156847088">Käytä tietokoneitasi turvallisesti Android-laitteella.</translation>
+<translation id="3194245623920924351">Chrome-etäkäyttö</translation>
+<translation id="3649256019230929621">Pienennä ikkuna</translation>
<translation id="4808503597364150972">Anna kohteen <ph name="HOSTNAME"/> PIN-koodi.</translation>
<translation id="7672203038394118626">Tietokoneen etäyhteydet on poistettu käytöstä.</translation>
<translation id="8244400547700556338">Lisätietoja.</translation>
@@ -27,7 +30,10 @@ isäntä</translation>
<translation id="7658239707568436148">Peruuta</translation>
<translation id="7782471917492991422">Tarkista tietokoneen virranhallinta-asetukset ja varmista, että sitä ei ole määritetty siirtymään virransäästötilaan, kun sitä ei käytetä.</translation>
<translation id="7665369617277396874">Lisää tili</translation>
+<translation id="5925497314631808737">• Koko näytön tila / immersiivinen tila lisätty.
+• Toimintapalkin piilotuskuvaketta parannettu.</translation>
<translation id="2707879711568641861">Jotkin Chrome-etäkäyttöön vaadittavat komponentit puuttuvat. Varmista, että olet asentanut uusimman Google Chrome -version, ja yritä uudelleen.</translation>
+<translation id="1779766957982586368">Sulje ikkuna</translation>
<translation id="2499160551253595098">Auta meitä parantamaan Chrome-etäkäyttöä antamalla meidän kerätä käyttötilastoja ja virheraportteja.</translation>
<translation id="7868137160098754906">Anna etätietokoneen PIN-koodi.</translation>
<translation id="677755392401385740">Käyttäjän <ph name="HOST_USERNAME"/> isäntä käynnistettiin.</translation>
@@ -134,7 +140,6 @@ Lisätietoja tietosuojasta on Googlen tietosuojakäytännössä (http://goo.gl/S
<translation id="4361728918881830843">Ota etäyhteys käyttöön asentamalla toiselle tietokoneelle Chrome-etäkäyttö ja valitsemalla <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Asiakas <ph name="CLIENT_USERNAME"/> yhdistettiin.</translation>
<translation id="4913529628896049296">odotetaan yhteyttä...</translation>
-<translation id="5714695932513333758">Yhteyshistoria</translation>
<translation id="811307782653349804">Käytä tietokonettasi mistä haluat.</translation>
<translation id="2939145106548231838">Todenna isännälle</translation>
<translation id="2366718077645204424">Isäntään ei saatu yhteyttä. Tämä johtuu luultavasti käyttämäsi verkon määrityksistä.</translation>
@@ -189,7 +194,6 @@ isäntä</translation>
<translation id="5308380583665731573">Yhdistä</translation>
<translation id="7319983568955948908">Lopeta jakaminen</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Kaikki oikeudet pidätetään.</translation>
-<translation id="8038108135592087998">Chrome-etäkäytön Android-version ensimmäinen julkaisu.</translation>
<translation id="6668065415969892472">PIN-koodi on päivitetty.</translation>
<translation id="4513946894732546136">Palaute</translation>
<translation id="4277736576214464567">Käyttökoodi on virheellinen. Yritä uudelleen.</translation>
@@ -214,5 +218,6 @@ isäntä</translation>
<translation id="1818475040640568770">Et ole rekisteröinyt tietokoneita.</translation>
<translation id="4394049700291259645">Poista käytöstä</translation>
<translation id="4156740505453712750">Suojaa tämän tietokoneen käyttöä valitsemalla <ph name="BOLD_START"/>vähintään kuusinumeroinen<ph name="BOLD_END"/> PIN-koodi. Tarvitset PIN-koodin muodostaessasi yhteyden toiselta koneelta.</translation>
+<translation id="6398765197997659313">Poistu koko näytön tilasta</translation>
<translation id="3286521253923406898">Chromoting-isännän hallinta</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_fil.xtb b/remoting/resources/remoting_strings_fil.xtb
index b0fd2851c4..756bce84be 100644
--- a/remoting/resources/remoting_strings_fil.xtb
+++ b/remoting/resources/remoting_strings_fil.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">Nakakita ng hindi tugmang bersyon ng Remote na Desktop ng Chrome. Mangyaring tiyaking na mayroon ka ng pinakabagong bersyon ng Chrome at Remote na Desktop ng Chrome sa parehong computer at subukang muli.</translation>
<translation id="6998989275928107238">Para kay</translation>
<translation id="406849768426631008">Gustong tulungan ang isang tao habang nagvi-video chat rin kasama nila? Subukan ang <ph name="LINK_BEGIN"/> Remote na Desktop sa Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">I-maximize ang window</translation>
<translation id="5397086374758643919">Uninstaller ng Host ng Remote na Desktop ng Chrome</translation>
<translation id="3258789396564295715">Maaari mong ligtas na ma-access ang computer na ito gamit ang Remote na Desktop ng Chrome.</translation>
<translation id="5070121137485264635">Hinihiling sa iyo ng remote host na magpatunay sa isang website ng third-party. Upang makapagpatuloy, dapat mong bigyan ng mga karagdagang pahintulot ang Remote na Desktop ng Chrome upang ma-access ang address na ito:</translation>
<translation id="2124408767156847088">Ligtas na i-access ang iyong mga computer mula sa iyong Android device.</translation>
+<translation id="3194245623920924351">Remote na Desktop ng Chrome</translation>
+<translation id="3649256019230929621">I-minimize ang window</translation>
<translation id="4808503597364150972">Pakilagay ang iyong PIN para sa <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Hindi pinagana ang mga malayuang koneksyon para sa computer na ito.</translation>
<translation id="8244400547700556338">Matutunan kung paano.</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Kanselahin</translation>
<translation id="7782471917492991422">Pakisuri ang mga setting sa pamamahala ng power ng iyong computer at tiyakin na hindi ito naka-configure na mag-sleep kapag idle.</translation>
<translation id="7665369617277396874">Magdagdag ng account</translation>
+<translation id="5925497314631808737">• Ipinatupad na full-screen / immersive mode.
+• Pinahusay na icon sa pagtatago sa action bar.</translation>
<translation id="2707879711568641861">Nawawala ang ilang bahagi na kinakailangan para sa Remote na Desktop ng Chrome. Mangyaring tiyakin na na-install mo na ang pinakabagong bersyon at subukang muli.</translation>
+<translation id="1779766957982586368">Isara ang window</translation>
<translation id="2499160551253595098">Tumulong sa amin na pahusayin ang Remote na Desktop ng Chrome sa pamamagitan ng pagbibigay-daan sa amin na mangolekta ng mga statistics sa paggamit at mga ulat ng pag-crash.</translation>
<translation id="7868137160098754906">Pakilagay ang iyong PIN para sa remote na computer.</translation>
<translation id="677755392401385740">Sinimulan ang host para sa user: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Para sa higit pang impormasyon tungkol sa privacy, pakitingnan ang Patakaran sa
<translation id="4361728918881830843">Upang paganahin ang mga malayuang koneksyon sa ibang computer, i-install ang Remote na Desktop ng Chrome doon at i-click ang “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Nakakonekta ang client: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">naghihintay ng koneksyon…</translation>
-<translation id="5714695932513333758">Kasaysayan ng koneksyon</translation>
<translation id="811307782653349804">I-access ang sarili mong computer mula saanman.</translation>
<translation id="2939145106548231838">Patunayan upang ma-host</translation>
<translation id="2366718077645204424">Hindi maabot ang host. Malamang na dahil ito sa configuration ng network na iyong ginagamit.</translation>
@@ -189,7 +194,6 @@ Desktop ng Chrome</translation>
<translation id="5308380583665731573">Kumunekta</translation>
<translation id="7319983568955948908">Ihinto ang Pagbabahagi</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Nakalaan ang Lahat ng Karapatan.</translation>
-<translation id="8038108135592087998">Unang labas ng Remote na Desktop ng Chrome para sa Android.</translation>
<translation id="6668065415969892472">Na-update na ang iyong PIN.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">Di-wasto ang access code. Pakisubukang muli.</translation>
@@ -214,5 +218,6 @@ Desktop ng Chrome</translation>
<translation id="1818475040640568770">Wala kang mga computer na nakarehistro.</translation>
<translation id="4394049700291259645">Huwag paganahin</translation>
<translation id="4156740505453712750">Upang protektahan ang access sa computer na ito, mangyaring pumili ng PIN na may <ph name="BOLD_START"/>hindi bababa sa anim na digit<ph name="BOLD_END"/>. Kakailanganin ang PIN na ito kapag kumokonekta mula sa isa pang lokasyon.</translation>
+<translation id="6398765197997659313">Lumabas sa buong screen</translation>
<translation id="3286521253923406898">Controller ng Chromoting Host</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_fr.xtb b/remoting/resources/remoting_strings_fr.xtb
index ab17876ed8..032cf6e8b5 100644
--- a/remoting/resources/remoting_strings_fr.xtb
+++ b/remoting/resources/remoting_strings_fr.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Une version incompatible du bureau à distance Google Chrome a été détectée. Veuillez vous assurer que vous utilisez la dernière version de Google Chrome et du bureau à distance sur les deux ordinateurs, puis réessayer.</translation>
<translation id="6998989275928107238">À</translation>
<translation id="406849768426631008">Vous voulez aider une personne au cours d'un chat vidéo avec elle ? Essayez <ph name="LINK_BEGIN"/> le Bureau à distance dans Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Agrandir la fenêtre</translation>
<translation id="5397086374758643919">Programme de désinstallation de l'hôte Bureau à distance Google Chrome</translation>
<translation id="3258789396564295715">Vous pouvez accéder à cet ordinateur en toute sécurité via le bureau à distance Google Chrome.</translation>
<translation id="5070121137485264635">L'hôte distant nécessite votre authentification auprès d'un site Web tiers. Pour continuer, vous devez accorder au Bureau à distance Chrome des autorisations supplémentaires afin d'accéder à l'adresse suivante :</translation>
<translation id="2124408767156847088">Accédez à vos ordinateurs en toute sécurité depuis votre appareil Android</translation>
+<translation id="3194245623920924351">Bureau à distance Chrome</translation>
+<translation id="3649256019230929621">Réduire la fenêtre</translation>
<translation id="4808503597364150972">Veuillez saisir le code d'accès pour <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Les connexions à distance ont été désactivées pour cet ordinateur.</translation>
<translation id="8244400547700556338">En savoir plus</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Annuler</translation>
<translation id="7782471917492991422">Veuillez vérifier les paramètres de gestion d'alimentation de votre ordinateur et vous assurer qu'il n'est pas configuré pour se mettre en veille en cas d'inactivité.</translation>
<translation id="7665369617277396874">Ajouter un compte</translation>
+<translation id="5925497314631808737">• Implémentation du plein écran/mode immersif
+• Amélioration de l'icône permettant de masquer la barre d'action</translation>
<translation id="2707879711568641861">Certains composants nécessaires à l'utilisation du bureau à distance Google Chrome sont introuvables. Veuillez vous assurer que vous utilisez la dernière version, puis réessayer.</translation>
+<translation id="1779766957982586368">Fermer la fenêtre</translation>
<translation id="2499160551253595098">Aidez-nous à améliorer le bureau à distance Google Chrome en nous autorisant à recueillir des statistiques d'utilisation et des rapports d'erreur.</translation>
<translation id="7868137160098754906">Veuillez saisir le code d'accès de l'ordinateur distant.</translation>
<translation id="677755392401385740">Hôte démarré pour l'utilisateur &quot;<ph name="HOST_USERNAME"/>&quot;</translation>
@@ -134,7 +140,6 @@ Pour en savoir plus sur la confidentialité, veuillez consulter les règles de c
<translation id="4361728918881830843">Pour activer les connexions à distance à un autre ordinateur, installez l'application Bureau à distance Google Chrome sur celui-ci, puis cliquez sur &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Client connecté : <ph name="CLIENT_USERNAME"/></translation>
<translation id="4913529628896049296">en attente de connexion…</translation>
-<translation id="5714695932513333758">Historique des connexions</translation>
<translation id="811307782653349804">Accédez à votre ordinateur où que vous soyez.</translation>
<translation id="2939145106548231838">S'authentifier auprès de l'hôte</translation>
<translation id="2366718077645204424">Impossible de joindre l'hôte, probablement en raison de la configuration du réseau que vous utilisez.</translation>
@@ -189,7 +194,6 @@ Google Chrome</translation>
<translation id="5308380583665731573">Se connecter</translation>
<translation id="7319983568955948908">Arrêter le partage</translation>
<translation id="6681800064886881394">Copyright © Google Inc. 2013. Tous droits réservés.</translation>
-<translation id="8038108135592087998">Première version du Bureau à distance Chrome pour Android</translation>
<translation id="6668065415969892472">Votre code d'accès a été mis à jour.</translation>
<translation id="4513946894732546136">Commentaires</translation>
<translation id="4277736576214464567">Le code d'accès n'est pas valide. Veuillez réessayer.</translation>
@@ -214,5 +218,6 @@ Google Chrome</translation>
<translation id="1818475040640568770">Vous n'avez pas d'ordinateurs enregistrés.</translation>
<translation id="4394049700291259645">Désactiver</translation>
<translation id="4156740505453712750">Pour protéger l'accès à cet ordinateur, veuillez choisir un code d'accès d'<ph name="BOLD_START"/>au moins six chiffres<ph name="BOLD_END"/>. Ce code d'accès sera demandé lors d'une connexion depuis un autre appareil.</translation>
+<translation id="6398765197997659313">Quitter le mode plein écran</translation>
<translation id="3286521253923406898">Contrôleur de l'hôte Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_hi.xtb b/remoting/resources/remoting_strings_hi.xtb
index 315816a12f..8ded8cdee4 100644
--- a/remoting/resources/remoting_strings_hi.xtb
+++ b/remoting/resources/remoting_strings_hi.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª के असंगत संसà¥à¤•à¤°à¤£ का पता लगा था. कृपया सà¥à¤¨à¤¿à¤¶à¥à¤šà¤¿à¤¤ करें कि आपके पास दोनों कंपà¥â€à¤¯à¥‚टर पर Chrome और Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª के नवीनतम संसà¥â€à¤•à¤°à¤£ हैं और पà¥à¤¨à¤ƒ पà¥à¤°à¤¯à¤¾à¤¸ करें.</translation>
<translation id="6998989275928107238">पà¥à¤°à¤¤à¤¿</translation>
<translation id="406849768426631008">किसी के साथ वीडियो बातचीत करते हà¥à¤ उनकी सहायता भी करना चाहते हैं? <ph name="LINK_BEGIN"/> Google Hangout में रिमोट डेसà¥à¤•à¤Ÿà¥‰à¤ª<ph name="LINK_END"/> आज़माà¤à¤‚.</translation>
+<translation id="906458777597946297">विंडो को बड़ा करें</translation>
<translation id="5397086374758643919">Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª होसà¥à¤Ÿ अनइंसà¥à¤Ÿà¥‰à¤²à¤°</translation>
<translation id="3258789396564295715">आप Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª का उपयोग करके सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ रूप से इस कंपà¥à¤¯à¥‚टर को à¤à¤•à¥à¤¸à¥‡à¤¸ कर सकते हैं.</translation>
<translation id="5070121137485264635">दूरसà¥à¤¥ होसà¥à¤Ÿ के लिठआवशà¥à¤¯à¤• है कि आप किसी तृतीय-पकà¥à¤· की वेबसाइट पà¥à¤°à¤®à¤¾à¤£à¥€à¤•à¥ƒà¤¤ करें. जारी रखने के लिà¤, आपको Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª को इस पते को à¤à¤•à¥à¤¸à¥‡à¤¸ करने के लिठअतिरिकà¥à¤¤ अनà¥à¤®à¤¤à¤¿à¤¯à¤¾à¤‚ देनी होंगी:</translation>
<translation id="2124408767156847088">अपने Android उपकरण से सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ रूप से अपने कंपà¥à¤¯à¥‚टर à¤à¤•à¥à¤¸à¥‡à¤¸ करें.</translation>
+<translation id="3194245623920924351">Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª</translation>
+<translation id="3649256019230929621">विंडो को छोटा करें</translation>
<translation id="4808503597364150972">कृपया <ph name="HOSTNAME"/> के लिठअपना पिन दरà¥à¤œ करें.</translation>
<translation id="7672203038394118626">इस कंपà¥â€à¤¯à¥‚टर के लिठदूरसà¥â€à¤¥ कनेकà¥â€à¤¶à¤¨ अकà¥à¤·à¤® कर दिठगठहैं.</translation>
<translation id="8244400547700556338">जानें कैसे.</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">रदà¥à¤¦ करें</translation>
<translation id="7782471917492991422">कृपया अपने कंपà¥â€à¤¯à¥‚टर की शकà¥à¤¤à¤¿ पà¥à¤°à¤¬à¤‚धन सेटिंग जांचें और सà¥à¤¨à¤¿à¤¶à¥à¤šà¤¿à¤¤ करें कि उसे पà¥à¤°à¤¯à¥‹à¤— में नहीं रहते समय सà¥à¤ªà¥à¤¤ रहने के लिठकॉनà¥â€à¤«à¤¼à¤¿à¤—र नहीं किया गया है.</translation>
<translation id="7665369617277396874">खाता जोड़ें</translation>
+<translation id="5925497314631808737">• लागू पूरà¥à¤£-सà¥â€à¤•à¥à¤°à¥€à¤¨ / इमरà¥à¤¸à¤¿à¤µ मोड.
+• कारà¥à¤°à¤µà¤¾à¤ˆ बार को छिपाने के लिठबेहतर आइकन.</translation>
<translation id="2707879711568641861">Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª के लिठआवशà¥â€à¤¯à¤• कà¥à¤› घटक गà¥à¤® हैं. कृपया सà¥à¤¨à¤¿à¤¶à¥à¤šà¤¿à¤¤ करें कि आपने नवीनतम संसà¥à¤•à¤°à¤£ इंसà¥â€à¤Ÿà¥‰à¤² किया है और पà¥à¤¨à¤ƒ पà¥à¤°à¤¯à¤¾à¤¸ करें.</translation>
+<translation id="1779766957982586368">विंडो बंद करें</translation>
<translation id="2499160551253595098">हमें उपयोग आंकड़े और कà¥à¤°à¥ˆà¤¶ रिपोरà¥à¤Ÿ à¤à¤•à¤¤à¥à¤°à¤¿à¤¤ करने की अनà¥à¤®à¤¤à¤¿ देते हà¥à¤ Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª को सà¥à¤§à¤¾à¤°à¤¨à¥‡ में हमारी सहायता करें.</translation>
<translation id="7868137160098754906">कृपया दूरसà¥à¤¥ कंपà¥à¤¯à¥‚टर के लिठअपना पिन डालें.</translation>
<translation id="677755392401385740">होसà¥à¤Ÿ, इस उपयोगकरà¥à¤¤à¤¾ के लिठपà¥à¤°à¤¾à¤°à¤‚भ किया गया: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">किसी भिनà¥à¤¨ कंपà¥à¤¯à¥‚टर हेतॠदूरसà¥à¤¥ कनेकà¥à¤¶à¤¨ सकà¥à¤·à¤® करने के लिà¤, वहां Chrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª इंसà¥à¤Ÿà¥‰à¤² करें और “<ph name="BUTTON_NAME"/>†कà¥à¤²à¤¿à¤• करें.</translation>
<translation id="7444276978508498879">कà¥à¤²à¤¾à¤‡à¤‚ट कनेकà¥à¤Ÿ किया गया: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">कनेकà¥à¤¶à¤¨ के लिठपà¥à¤°à¤¤à¥€à¤•à¥à¤·à¤¾à¤°à¤¤â€¦</translation>
-<translation id="5714695932513333758">कनेकà¥à¤¶à¤¨ इतिहास</translation>
<translation id="811307782653349804">अपने कंपà¥à¤¯à¥‚टर पर कहीं से भी पहà¥à¤‚चें.</translation>
<translation id="2939145106548231838">होसà¥à¤Ÿ करने के लिठपà¥à¤°à¤®à¤¾à¤£à¥€à¤•à¥ƒà¤¤ करें</translation>
<translation id="2366718077645204424">होसà¥â€à¤Ÿ तक पहà¥à¤‚चने में असमरà¥à¤¥. यह संभवत: आपके दà¥à¤µà¤¾à¤°à¤¾ उपयोग किठजाने वाले नेटवरà¥à¤• के कॉनà¥à¤«à¤¼à¤¿à¤—रेशन के कारण है.</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">कनेकà¥à¤Ÿ करें</translation>
<translation id="7319983568955948908">साà¤à¤¾à¤•à¤°à¤£ रोकें</translation>
<translation id="6681800064886881394">कॉपीराइट 2013 Google Inc. सरà¥à¤µà¤¾à¤§à¤¿à¤•à¤¾à¤° सà¥à¤°à¤•à¥à¤·à¤¿à¤¤.</translation>
-<translation id="8038108135592087998">Android के लिठChrome दूरसà¥à¤¥ डेसà¥à¤•à¤Ÿà¥‰à¤ª की पहली रिलीज़.</translation>
<translation id="6668065415969892472">आपका पिन अपडेट कर दिया गया है.</translation>
<translation id="4513946894732546136">फ़ीडबैक</translation>
<translation id="4277736576214464567">पहà¥à¤‚च कोड अमानà¥à¤¯ है. कृपया पà¥à¤¨à¤ƒ पà¥à¤°à¤¯à¤¾à¤¸ करें.</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">आपके पास कोई कंपà¥à¤¯à¥‚टर पंजीकृत नहीं है.</translation>
<translation id="4394049700291259645">अकà¥à¤·à¤® करें</translation>
<translation id="4156740505453712750">इस कंपà¥à¤¯à¥‚टर की पहà¥à¤‚च सà¥à¤°à¤•à¥à¤·à¤¿à¤¤ करने के लिà¤, कृपया <ph name="BOLD_START"/>कम से कम छ: अंक<ph name="BOLD_END"/> का पिन चà¥à¤¨à¥‡à¤‚. यह पिन किसी अनà¥à¤¯ सà¥à¤¥à¤¾à¤¨ से कनेकà¥à¤Ÿ करते समय आवशà¥à¤¯à¤• होगा.</translation>
+<translation id="6398765197997659313">पूरà¥à¤£ सà¥à¤•à¥à¤°à¥€à¤¨ से बाहर निकलें</translation>
<translation id="3286521253923406898">Chromoting होसà¥à¤Ÿ नियंतà¥à¤°à¤•</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_hr.xtb b/remoting/resources/remoting_strings_hr.xtb
index 7121370472..1f29a4a341 100644
--- a/remoting/resources/remoting_strings_hr.xtb
+++ b/remoting/resources/remoting_strings_hr.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Otkrivena je nekompatibilna verzija aplikacije Udaljena radna povrÅ¡ina Chrome. Provjerite imate li na oba raÄunala najnoviju verziju sustava Chrome i aplikacije Udaljena radna povrÅ¡ina Chrome, a zatim pokuÅ¡ajte ponovo.</translation>
<translation id="6998989275928107238">Prima</translation>
<translation id="406849768426631008">Želite li pomoći nekome tijekom videochata? Isprobajte <ph name="LINK_BEGIN"/> Udaljenu radnu površinu na usluzi Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksimiziranje prozora</translation>
<translation id="5397086374758643919">Program za deinstaliranje hosta Udaljene radne površine Chrome</translation>
<translation id="3258789396564295715">Ovom raÄunalu možete sigurno pristupiti pomoću Udaljene radne povrÅ¡ine Chrome.</translation>
<translation id="5070121137485264635">Udaljeni host zahtijeva autentifikaciju web-lokacije treće strane. Da biste nastavili, morate dati Udaljenoj radnoj površini Chrome dodatne dozvole za pristup ovoj adresi:</translation>
<translation id="2124408767156847088">Sigurno pristupajte svojim raÄunalima s ureÄ‘aja sa sustavom Android.</translation>
+<translation id="3194245623920924351">Udaljena radna površina Chrome</translation>
+<translation id="3649256019230929621">Minimiziranje prozora</translation>
<translation id="4808503597364150972">Unesite svoj PIN za host <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Na ovom su raÄunalu onemogućene daljinske veze.</translation>
<translation id="8244400547700556338">Saznajte kako.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Odustani</translation>
<translation id="7782471917492991422">Provjerite postavke upravljanja potroÅ¡njom energije svojeg raÄunala i provjerite da nije konfigurirano tako da prelazi u stanje mirovanja kad je neaktivno.</translation>
<translation id="7665369617277396874">Dodaj raÄun</translation>
+<translation id="5925497314631808737">• Primijenjen je naÄin prikazivanja na cijelom zaslonu/interaktivni naÄin.
+• Poboljšana je ikona za sakrivanje trake radnji.</translation>
<translation id="2707879711568641861">Nedostaju neke komponente potrebne za aplikaciju Udaljena radna površina Chrome. Provjerite jeste li instalirali najnoviju verziju i pokušajte ponovo.</translation>
+<translation id="1779766957982586368">Zatvori prozor</translation>
<translation id="2499160551253595098">Pomognite nam poboljšati Udaljenu radnu površinu Chrome, dopustite nam prikupljanje statistike upotrebe i izvješća o padu programa.</translation>
<translation id="7868137160098754906">Unesite PIN za udaljeno raÄunalo.</translation>
<translation id="677755392401385740">Host je pokrenut za korisnika: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Informacije o privatnosti potražite u Googleovim pravilima o privatnosti (http:
<translation id="4361728918881830843">Da biste omogućili povezivanje s razliÄitim udaljenim raÄunalima, na njima instalirajte Udaljenu radnu povrÅ¡inu Chrome pa kliknite gumb &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Povezani klijent: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Äekanje na vezu...</translation>
-<translation id="5714695932513333758">Povijest veza</translation>
<translation id="811307782653349804">Pristupite svojem raÄunalu s bilo kojeg mjesta.</translation>
<translation id="2939145106548231838">Autentifikacija na hostu</translation>
<translation id="2366718077645204424">Nije moguće pristupiti hostu. To je vjerojatno zbog konfiguracije mreže koju upotrebljavate.</translation>
@@ -189,7 +194,6 @@ površine Chrome</translation>
<translation id="5308380583665731573">Poveži se</translation>
<translation id="7319983568955948908">Zaustavi dijeljenje</translation>
<translation id="6681800064886881394">Autorska prava 2013. Google Inc. Sva prava pridržana.</translation>
-<translation id="8038108135592087998">Prvo izdanje Udaljene radne površine Chrome za Android.</translation>
<translation id="6668065415969892472">Ažuriran je PIN.</translation>
<translation id="4513946894732546136">Povratne informacije</translation>
<translation id="4277736576214464567">Pristupni je kôd nevažeći. Pokušajte ponovo.</translation>
@@ -214,5 +218,6 @@ površine Chrome</translation>
<translation id="1818475040640568770">Nemate registriranih raÄunala.</translation>
<translation id="4394049700291259645">Onemogući</translation>
<translation id="4156740505453712750">Da biste zaÅ¡titili pristup ovom raÄunalu, odaberite PIN s <ph name="BOLD_START"/>najmanje Å¡est brojeva<ph name="BOLD_END"/>. Taj će PIN biti potreban pri povezivanju s druge lokacije.</translation>
+<translation id="6398765197997659313">Izađi iz cijelog zaslona</translation>
<translation id="3286521253923406898">Kontroler hosta usluge Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_hu.xtb b/remoting/resources/remoting_strings_hu.xtb
index 09bc7d6d6b..b981c1290d 100644
--- a/remoting/resources/remoting_strings_hu.xtb
+++ b/remoting/resources/remoting_strings_hu.xtb
@@ -13,10 +13,13 @@ gazdagép</translation>
<translation id="2801119484858626560">A Chrome távoliasztal-szolgáltatás egy nem kompatibilis verziója található a számítógépen. Kérjük, ellenőrizze, hogy mindkét számítógépen a Chrome és a Chrome távoliasztal-szolgáltatás legfrissebb verziói futnak-e, majd próbálja újra.</translation>
<translation id="6998989275928107238">Ide:</translation>
<translation id="406849768426631008">Szeretne segíteni valakinek, miközben egyúttal videocsevegést is folytatnak? Próbálja ki a <ph name="LINK_BEGIN"/>Google Hangouts távoliasztal-szolgáltatását<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Ablak teljes méretre állítása</translation>
<translation id="5397086374758643919">Chrome távoliasztal-szolgáltatási gazdagép eltávolítóprogramja</translation>
<translation id="3258789396564295715">Biztonságosan hozzáférhet a számítógéphez a Chrome távoliasztal-szolgáltatás segítségével.</translation>
<translation id="5070121137485264635">A távoli gazdagép hitelesítést kér egy harmadik féltől származó webhelyhez. A folytatáshoz további engedélyeket kell adnia a Chrome távoliasztal-szolgáltatásnak a cím eléréséhez:</translation>
<translation id="2124408767156847088">Számítógépek biztonságos elérése androidos eszközéről</translation>
+<translation id="3194245623920924351">Chrome távoliasztal-szolgáltatás</translation>
+<translation id="3649256019230929621">Ablak kis méretre állítása</translation>
<translation id="4808503597364150972">Kérjük, adja meg a(z) <ph name="HOSTNAME"/> PIN kódját.</translation>
<translation id="7672203038394118626">A távoli kapcsolatok le vannak tiltva ezen a számítógépen.</translation>
<translation id="8244400547700556338">Tudja meg, hogyan.</translation>
@@ -27,7 +30,10 @@ gazdagép</translation>
<translation id="7658239707568436148">Mégse</translation>
<translation id="7782471917492991422">Kérjük, ellenőrizze a számítógép energiagazdálkodási beállításait, és győződjön meg róla, hogy az tétlenség esetén nincs alvó üzemmódra állítva.</translation>
<translation id="7665369617277396874">Fiók hozzáadása</translation>
+<translation id="5925497314631808737">• Megvalósított teljes képernyős/kiterjesztett mód.
+• Továbbfejlesztett ikon a műveletsáv elrejtéséhez.</translation>
<translation id="2707879711568641861">A Chrome távoliasztal-szolgáltatás egyes szükséges összetevői hiányoznak. Kérjük, ellenőrizze, hogy telepítve van-e a legfrissebb verzió, majd próbálja újra.</translation>
+<translation id="1779766957982586368">Ablak bezárása</translation>
<translation id="2499160551253595098">Segítsen fejleszteni a Chrome távoliasztal-szolgáltatást azáltal, hogy rendelkezésünkre bocsátja a használati statisztikákat és hibajelentéseket.</translation>
<translation id="7868137160098754906">Kérjük, adja meg a PIN kódot a távoli számítógéphez.</translation>
<translation id="677755392401385740">Gazdagép elindítva a következő felhasználó számára: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Adatvédelmi információkért tekintse meg a Google adatvédelmi irányelveit (
<translation id="4361728918881830843">Ahhoz, hogy engedélyezhesse a távoli kapcsolatokat egy másik számítógépen, telepítse a gépre a Chrome távoliasztal-szolgáltatást, majd kattintson a következÅ‘ gombra: „<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Kliens csatlakoztatva: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">várakozás a kapcsolatra...</translation>
-<translation id="5714695932513333758">Kapcsolatelőzmények</translation>
<translation id="811307782653349804">Bárhonnan hozzáférhet saját számítógépéhez.</translation>
<translation id="2939145106548231838">Hitelesítés a gazdagépen</translation>
<translation id="2366718077645204424">A gazdagép nem érhető el. Ez valószínűleg az Ön által használt hálózat konfigurációja miatt van.</translation>
@@ -189,7 +194,6 @@ gazdagép</translation>
<translation id="5308380583665731573">Csatlakozás</translation>
<translation id="7319983568955948908">Megosztás leállítása</translation>
<translation id="6681800064886881394">Copyright 2013 – Google Inc. Minden jog fenntartva.</translation>
-<translation id="8038108135592087998">A Chrome távoliasztal-szolgáltatás androidos verziójának első kiadása.</translation>
<translation id="6668065415969892472">A PIN kód frissült.</translation>
<translation id="4513946894732546136">Visszajelzés</translation>
<translation id="4277736576214464567">A hozzáférési kód érvénytelen. Kérjük, próbálja újra.</translation>
@@ -214,5 +218,6 @@ gazdagép</translation>
<translation id="1818475040640568770">Nincs regisztrált számítógépe.</translation>
<translation id="4394049700291259645">Kikapcsolás</translation>
<translation id="4156740505453712750">A számítógép hozzáférésének védelméhez válasszon egy <ph name="BOLD_START"/>legalább hat számjegyből álló<ph name="BOLD_END"/> PIN kódot. Ezt a PIN kódot kéri a rendszer, ha más helyről kísérlik meg a kapcsolódást.</translation>
+<translation id="6398765197997659313">Kilépés a teljes képernyős módból</translation>
<translation id="3286521253923406898">Chromoting gazdagépvezérlő</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_id.xtb b/remoting/resources/remoting_strings_id.xtb
index 9367ea9e7f..8fff9b3c2b 100644
--- a/remoting/resources/remoting_strings_id.xtb
+++ b/remoting/resources/remoting_strings_id.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Versi Chrome Desktop Jarak Jauh yang tidak kompatibel terdeteksi. Pastikan bahwa Anda memiliki Chrome dan Chrome Desktop Jarak Jauh versi teranyar di kedua komputer, lalu coba lagi.</translation>
<translation id="6998989275928107238">Ke</translation>
<translation id="406849768426631008">Ingin membantu seseorang sambil video chat dengannya? Coba <ph name="LINK_BEGIN"/> Desktop Jarak Jauh di Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Memaksimalkan jendela</translation>
<translation id="5397086374758643919">Pencopot Pemasangan Host Chrome Desktop Jarak Jauh</translation>
<translation id="3258789396564295715">Mungkin Anda dapat mengakses komputer ini dengan aman menggunakan Chrome Desktop Jarak Jauh.</translation>
<translation id="5070121137485264635">Hosting jarak jauh mewajibkan Anda mengautentikasi ke situs web pihak ketiga. Untuk melanjutkan, Anda harus memberikan izin tambahan ke Chrome Desktop Jarak Jauh untuk mengakses alamat ini:</translation>
<translation id="2124408767156847088">Akses komputer dari perangkat Android Anda dengan aman.</translation>
+<translation id="3194245623920924351">Chrome Desktop Jarak Jauh</translation>
+<translation id="3649256019230929621">Meminimalkan jendela</translation>
<translation id="4808503597364150972">Masukkan PIN Anda untuk <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Sambungan jarak jauh untuk komputer ini telah dinonaktifkan.</translation>
<translation id="8244400547700556338">Pelajari caranya.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Batal</translation>
<translation id="7782471917492991422">Periksa setelan pengelolaan daya komputer Anda dan pastikan agar tidak dikonfigurasi untuk tidur ketika menganggur.</translation>
<translation id="7665369617277396874">Tambahkan akun</translation>
+<translation id="5925497314631808737">• Penerapan layar penuh / mode yang lebih mendalam.
+• Peningkatan ikon untuk menyembunyikan bilah tindakan.</translation>
<translation id="2707879711568641861">Beberapa komponen yang diperlukan untuk Chrome Desktop Jarak Jauh hilang. Pastikan bahwa Anda telah memasang versi teranyar dan coba lagi.</translation>
+<translation id="1779766957982586368">Tutup jendela</translation>
<translation id="2499160551253595098">Bantu kami meningkatkan Chrome Desktop Jarak Jauh dengan mengizinkan kami mengumpulkan statistik penggunaan dan laporan kerusakan.</translation>
<translation id="7868137160098754906">Masukkan PIN komputer jarak jauh Anda.</translation>
<translation id="677755392401385740">Host dimulai untuk pengguna: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Untuk informasi tentang privasi, lihat Kebijakan Privasi Google (http://goo.gl/S
<translation id="4361728918881830843">Untuk mengaktifkan sambungan jarak jauh ke komputer yang berbeda, pasang Chrome Desktop Jarak Jauh di komputer tersebut, lalu klik “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Klien tersambung: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">menunggu sambungan...</translation>
-<translation id="5714695932513333758">Riwayat sambungan</translation>
<translation id="811307782653349804">Akses komputer Anda dari mana saja.</translation>
<translation id="2939145106548231838">Autentikasi untuk menghosting</translation>
<translation id="2366718077645204424">Tidak dapat menjangkau inang. Ini mungkin karena konfigurasi jaringan yang Anda gunakan.</translation>
@@ -189,7 +194,6 @@ Desktop Jarak Jauh</translation>
<translation id="5308380583665731573">Hubungkan</translation>
<translation id="7319983568955948908">Hentikan Berbagi</translation>
<translation id="6681800064886881394">Hak cipta 2013 Google Inc. Semua Hak Dilindungi Undang-Undang.</translation>
-<translation id="8038108135592087998">Rilis pertama Chrome Desktop Jarak Jauh untuk Android.</translation>
<translation id="6668065415969892472">PIN Anda telah diperbarui.</translation>
<translation id="4513946894732546136">Masukan</translation>
<translation id="4277736576214464567">Kode akses tidak valid. Harap coba lagi.</translation>
@@ -214,5 +218,6 @@ Desktop Jarak Jauh</translation>
<translation id="1818475040640568770">Anda tidak memiliki komputer yang terdaftar.</translation>
<translation id="4394049700291259645">Nonaktifkan</translation>
<translation id="4156740505453712750">Untuk melindungi akses ke komputer ini, gunakan PIN <ph name="BOLD_START"/>setidaknya enam digit<ph name="BOLD_END"/>. PIN ini akan diperlukan saat menyambung dari lokasi lainnya.</translation>
+<translation id="6398765197997659313">Keluar dari tampilan layar penuh</translation>
<translation id="3286521253923406898">Pengontrol Host Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_it.xtb b/remoting/resources/remoting_strings_it.xtb
index 3ecd890e45..582f872f65 100644
--- a/remoting/resources/remoting_strings_it.xtb
+++ b/remoting/resources/remoting_strings_it.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">È stata rilevata una versione incompatibile di Chrome Remote Desktop. Verifica che la versione più recente di Chrome e di Chrome Remote Desktop sia installata su entrambi i computer e riprova.</translation>
<translation id="6998989275928107238">A</translation>
<translation id="406849768426631008">Vuoi aiutare qualcuno mentre videochatti con lui? Prova <ph name="LINK_BEGIN"/>Desktop remoto in Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Ingrandisci la finestra</translation>
<translation id="5397086374758643919">Programma di disinstallazione host Chrome Remote Desktop</translation>
<translation id="3258789396564295715">Puoi accedere a questo computer in sicurezza utilizzando Chrome Remote Desktop.</translation>
<translation id="5070121137485264635">L'host remoto richiede l'autenticazione su un sito web di terze parti. Per continuare, devi concedere a Chrome Remote Desktop ulteriori autorizzazioni per accedere a questo indirizzo:</translation>
<translation id="2124408767156847088">Accedi in sicurezza ai tuoi computer dal tuo dispositivo Android.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Riduci la finestra</translation>
<translation id="4808503597364150972">Inserisci il PIN di <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Le connessioni remote per questo computer sono state disattivate.</translation>
<translation id="8244400547700556338">Scopri come.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Annulla</translation>
<translation id="7782471917492991422">Verifica le impostazioni di risparmio energia del computer e verifica che non sia configurata la sospensione per inattività.</translation>
<translation id="7665369617277396874">Aggiungi account</translation>
+<translation id="5925497314631808737">• È stata implementata la modalità immersiva/a schermo intero.
+• È stata migliorata l'icona per nascondere la barra delle azioni.</translation>
<translation id="2707879711568641861">Mancano alcuni componenti necessari per Chrome Remote Desktop. Verifica che sia installata la versione più recente e riprova.</translation>
+<translation id="1779766957982586368">Chiudi finestra</translation>
<translation id="2499160551253595098">Aiutaci a migliorare Chrome Remote Desktop consentendo l'acquisizione di dati per statistiche di utilizzo e rapporti sugli arresti anomali.</translation>
<translation id="7868137160098754906">Inserisci il codice PIN per il computer remoto.</translation>
<translation id="677755392401385740">Host avviato per utente: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Per informazioni sulla privacy, leggi le Norme sulla privacy di Google (http://g
<translation id="4361728918881830843">Per attivare le connessioni remote su un altro computer, installa Chrome Remote Desktop sul computer e fai clic su &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Client connesso: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">In attesa di connessione...</translation>
-<translation id="5714695932513333758">Cronologia connessioni</translation>
<translation id="811307782653349804">Accedi al tuo computer ovunque tu sia.</translation>
<translation id="2939145106548231838">Esegui autenticazione host</translation>
<translation id="2366718077645204424">Impossibile raggiungere l'host. Il problema potrebbe essere dovuto alla configurazione della rete in uso.</translation>
@@ -189,7 +194,6 @@ Remote Desktop</translation>
<translation id="5308380583665731573">Connetti</translation>
<translation id="7319983568955948908">Interrompi condivisione</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Tutti i diritti riservati.</translation>
-<translation id="8038108135592087998">Prima versione di Chrome Remote Desktop per Android.</translation>
<translation id="6668065415969892472">Il PIN è stato aggiornato.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">Il codice di accesso non è valido. Riprova.</translation>
@@ -214,5 +218,6 @@ Remote Desktop</translation>
<translation id="1818475040640568770">Non sono presenti computer registrati.</translation>
<translation id="4394049700291259645">Disabilita</translation>
<translation id="4156740505453712750">Per proteggere l'accesso a questo computer, scegli un PIN di <ph name="BOLD_START"/>almeno sei cifre<ph name="BOLD_END"/>. Questo PIN sarà richiesto quando ti connetti da un'altra posizione.</translation>
+<translation id="6398765197997659313">Esci da schermo intero</translation>
<translation id="3286521253923406898">Controller host Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_iw.xtb b/remoting/resources/remoting_strings_iw.xtb
index 691b11479d..a7c83d0412 100644
--- a/remoting/resources/remoting_strings_iw.xtb
+++ b/remoting/resources/remoting_strings_iw.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">â€×”מערכת זיהתה גירסה ×œ× ×ª×•×מת של 'שולחן עבודה מרוחק של Chrome'. ×•×“× ×©×‘×©× ×™ ×”×ž×—×©×‘×™× ×ž×•×ª×§× ×ª הגירסה העדכנית ביותר של Google Chrome ושל 'שולחן עבודה מרוחק של Chrome' ונסה שוב.</translation>
<translation id="6998989275928107238">×ל</translation>
<translation id="406849768426631008">â€×¨×•×¦×” לעזור למישהו תוך כדי ש×תה ×’× ×ž×§×™×™× ×תו שיחת ויד×ו צ'×ט? נסה ×ת <ph name="LINK_BEGIN"/> שולחן העבודה המרוחק ב-Google Hangoutsâ€<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">הגדל ×ת החלון</translation>
<translation id="5397086374758643919">â€×ž×¡×™×¨ ההתקנה של מ×רח שולחן העבודה המרוחק של Chromeâ€</translation>
<translation id="3258789396564295715">â€×ª×•×›×œ לגשת למחשב ×–×” ב×ופן מ×ובטח ב×מצעות 'שולחן עבודה מרוחק של Chrome'.</translation>
<translation id="5070121137485264635">â€×”מ×רח המרוחק דורש שתבצע ×ימות מול ×תר של צד שלישי. כדי להמשיך, עליך להעניק לשולחן העבודה המרוחק של Chrome הרש×ות נוספות כדי לגשת לכתובת זו:</translation>
<translation id="2124408767156847088">â€×’ש ×œ×ž×—×©×‘×™× ×©×œ×š ב×ופן מ×ובטח ממכשיר Android שלך.</translation>
+<translation id="3194245623920924351">â€×©×•×œ×—ן עבודה מרוחק של Chrome</translation>
+<translation id="3649256019230929621">מזער ×ת החלון</translation>
<translation id="4808503597364150972">â€×”זן ×ת ×”-PIN של <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">×—×™×‘×•×¨×™× ×ž×¨×•×—×§×™× ×œ×ž×—×©×‘ ×–×” הושבתו.</translation>
<translation id="8244400547700556338">למד כיצד.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">ביטול</translation>
<translation id="7782471917492991422">בדוק ×ת הגדרות ניהול צריכת החשמל של המחשב כדי ×œ×•×•×“× ×©×ינו מוגדר לעבור למצב שינה בזמן חוסר פעילות.</translation>
<translation id="7665369617277396874">הוסף חשבון</translation>
+<translation id="5925497314631808737">• הוחל מצב מסך ×ž×œ× / מצב עשיר.
+• הסמל להסתרת סרגל הפעולות שופר.</translation>
<translation id="2707879711568641861">â€×—לק ×ž×”×¨×›×™×‘×™× ×”×“×¨×•×©×™× ×¢×‘×•×¨ 'שולחן עבודה מרוחק של Chrome' חסרי×. ×•×“× ×©×”×ª×§× ×ª ×ת הגירסה העדכנית ביותר ונסה שוב.</translation>
+<translation id="1779766957982586368">סגור חלון</translation>
<translation id="2499160551253595098">â€×¢×–ור לנו לשפר ×ת 'שולחן עבודה מרוחק של Chrome' בכך שת×פשר לנו ל×סוף סטטיסטיקות שימוש ודוחות קריסה.</translation>
<translation id="7868137160098754906">â€×”זן ×ת מספר ×”-PIN עבור המחשב המרוחק.</translation>
<translation id="677755392401385740">המ×רח הופעל עבור המשתמש: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Chromoting</translation>
<translation id="4361728918881830843">â€×›×“×™ להפעיל ×—×™×‘×•×¨×™× ×ž×¨×—×•×§ למחשב ×חר, התקן ×ת 'שולחן עבודה מרוחק של Chrome' במחשב ×”×חר ולחץ על '<ph name="BUTTON_NAME"/>'.</translation>
<translation id="7444276978508498879">לקוח שהתחבר: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">ממתין לחיבור...</translation>
-<translation id="5714695932513333758">היסטוריית חיבורי×</translation>
<translation id="811307782653349804">גש למחשב שלך מכל מקו×.</translation>
<translation id="2939145106548231838">×מת מול המ×רח</translation>
<translation id="2366718077645204424">×œ× × ×™×ª×Ÿ להשיג ×ת המ×רח. הסיבה לכך יכולה להיות תצורת הרשת שבה ×תה משתמש.</translation>
@@ -189,7 +194,6 @@ Chromoting</translation>
<translation id="5308380583665731573">התחבר</translation>
<translation id="7319983568955948908">הפסק ×ת השיתוף</translation>
<translation id="6681800064886881394">â€Copyright © 2013 Google Inc.‎. כל הזכויות שמורות.</translation>
-<translation id="8038108135592087998">â€×”גרסה הר×שונה של שולחן העבודה המרוחק של Chrome עבור Android.</translation>
<translation id="6668065415969892472">â€×”-PIN שלך עודכן.</translation>
<translation id="4513946894732546136">משוב</translation>
<translation id="4277736576214464567">קוד הגישה ×ינו חוקי. נסה שוב.</translation>
@@ -214,5 +218,6 @@ Chromoting</translation>
<translation id="1818475040640568770">×ין לך ×ž×—×©×‘×™× ×¨×©×•×ž×™×.</translation>
<translation id="4394049700291259645">השבת</translation>
<translation id="4156740505453712750">â€×›×“×™ להגן על הגישה למחשב ×–×”, בחר PIN ב×ורך <ph name="BOLD_START"/>שש ספרות לפחות<ph name="BOLD_END"/>. PIN ×–×” יידרש בעת התחברות ×ž×ž×™×§×•× ×חר.</translation>
+<translation id="6398765197997659313">×¦× ×ž×ž×¡×š מל×</translation>
<translation id="3286521253923406898">â€×‘קר מ×רח Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_ja.xtb b/remoting/resources/remoting_strings_ja.xtb
index 97be8a9523..0bb89c6a89 100644
--- a/remoting/resources/remoting_strings_ja.xtb
+++ b/remoting/resources/remoting_strings_ja.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">互æ›æ€§ã®ãªã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã® Chrome リモート デスクトップãŒæ¤œå‡ºã•ã‚Œã¾ã—ãŸã€‚最新ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã® Chrome 㨠Chrome リモート デスクトップãŒä¸¡æ–¹ã®ãƒ‘ソコンã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。</translation>
<translation id="6998989275928107238">接続先</translation>
<translation id="406849768426631008">ä»–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã‚µãƒãƒ¼ãƒˆã‚’å¿…è¦ã¨ã—ã¦ã„ã‚‹ã¨ãã«ã€ãƒ“デオ ãƒãƒ£ãƒƒãƒˆã—ãªãŒã‚‰ã‚µãƒãƒ¼ãƒˆã§ãã¾ã™ã€‚<ph name="LINK_BEGIN"/>Google ãƒãƒ³ã‚°ã‚¢ã‚¦ãƒˆã®ãƒªãƒ¢ãƒ¼ãƒˆ デスクトップ機能<ph name="LINK_END"/>ã‚’ãŠè©¦ã—ãã ã•ã„。</translation>
+<translation id="906458777597946297">ウィンドウを最大化</translation>
<translation id="5397086374758643919">Chrome リモート デスクトップ ホストã®ã‚¢ãƒ³ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ©</translation>
<translation id="3258789396564295715">Chrome リモート デスクトップを使用ã—ã¦ã€ã“ã®ãƒ‘ソコンã«å®‰å…¨ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚</translation>
<translation id="5070121137485264635">リモート ホストã§ã€ã‚µãƒ¼ãƒ‰ãƒ‘ーティ ウェブサイトã¸ã®èªè¨¼ãŒæ±‚ã‚られã¦ã„ã¾ã™ã€‚続行ã™ã‚‹ã«ã¯ã€æ¬¡ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãるよã†ã« Chrome リモート デスクトップã«è¿½åŠ ã®æ¨©é™ã‚’与ãˆã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™:</translation>
<translation id="2124408767156847088">Androidæ­è¼‰ãƒ‡ãƒã‚¤ã‚¹ã‹ã‚‰ãƒ‘ソコンã«å®‰å…¨ã«ã‚¢ã‚¯ã‚»ã‚¹ã€‚</translation>
+<translation id="3194245623920924351">Chrome リモート デスクトップ</translation>
+<translation id="3649256019230929621">ウィンドウを最å°åŒ–</translation>
<translation id="4808503597364150972"><ph name="HOSTNAME"/> ã® PIN を入力ã—ã¦ãã ã•ã„。</translation>
<translation id="7672203038394118626">ã“ã®ãƒ‘ソコンã®ãƒªãƒ¢ãƒ¼ãƒˆæŽ¥ç¶šãŒç„¡åŠ¹ã«ãªã‚Šã¾ã—ãŸã€‚</translation>
<translation id="8244400547700556338">詳細を見る。</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">キャンセル</translation>
<translation id="7782471917492991422">ãŠä½¿ã„ã®ãƒ‘ソコンã®é›»æºç®¡ç†è¨­å®šã§ã€ã‚¢ã‚¤ãƒ‰ãƒ«çŠ¶æ…‹ã®ã¨ãã«ã‚¹ãƒªãƒ¼ãƒ—ã«ãªã‚‰ãªã„よã†ã«è¨­å®šã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’ã”確èªãã ã•ã„。</translation>
<translation id="7665369617277396874">アカウントを追加</translation>
+<translation id="5925497314631808737">• フルスクリーン/没入モードを実装ã—ã¾ã—ãŸã€‚
+• æ“作ãƒãƒ¼ã‚’éžè¡¨ç¤ºã«ã™ã‚‹ã‚¢ã‚¤ã‚³ãƒ³ã‚’改善ã—ã¾ã—ãŸã€‚</translation>
<translation id="2707879711568641861">Chrome リモート デスクトップã«å¿…è¦ãªã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。最新ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’確èªã—ã¦ã€ã‚‚ã†ä¸€åº¦ãŠè©¦ã—ãã ã•ã„。</translation>
+<translation id="1779766957982586368">ウィンドウを閉ã˜ã‚‹</translation>
<translation id="2499160551253595098">Chrome リモート デスクトップã®æ”¹å–„ã®ãŸã‚ã€ä½¿ç”¨çµ±è¨ˆæƒ…å ±ã¨ã‚¯ãƒ©ãƒƒã‚·ãƒ¥ レãƒãƒ¼ãƒˆã®åŽé›†ã«å”力ã™ã‚‹</translation>
<translation id="7868137160098754906">リモート パソコン㮠PIN を入力ã—ã¦ãã ã•ã„。</translation>
<translation id="677755392401385740">次ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’開始ã—ã¾ã—ãŸ: <ph name="HOST_USERNAME"/>。</translation>
@@ -134,7 +140,6 @@ US英語以外ã®ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã‚’使用ã—ã¦ã„るリモートパソコンã§
<translation id="4361728918881830843">別ã®ãƒ‘ソコンã¸ã®ãƒªãƒ¢ãƒ¼ãƒˆæŽ¥ç¶šã‚’有効ã«ã™ã‚‹ã«ã¯ã€å¯¾è±¡ã¨ãªã‚‹ãƒ‘ソコン㫠Chrome リモート デスクトップをインストールã—ã€[<ph name="BUTTON_NAME"/>] をクリックã—ã¦ãã ã•ã„。</translation>
<translation id="7444276978508498879">次ã®ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆãŒæŽ¥ç¶šã—ã¾ã—ãŸ: <ph name="CLIENT_USERNAME"/>。</translation>
<translation id="4913529628896049296">接続を待機ã—ã¦ã„ã¾ã™...</translation>
-<translation id="5714695932513333758">接続履歴</translation>
<translation id="811307782653349804">ã©ã“ã‹ã‚‰ã§ã‚‚自分ã®ãƒ‘ソコンã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã™ã€‚</translation>
<translation id="2939145106548231838">ホストã¸ã®èªè¨¼</translation>
<translation id="2366718077645204424">ホストã«ã‚¢ã‚¯ã‚»ã‚¹ã§ãã¾ã›ã‚“。ãŠä½¿ã„ã®ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã®è¨­å®šãŒåŽŸå› ã§ã‚ã‚‹ã“ã¨ãŒè€ƒãˆã‚‰ã‚Œã¾ã™ã€‚</translation>
@@ -189,7 +194,6 @@ US英語以外ã®ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã‚’使用ã—ã¦ã„るリモートパソコンã§
<translation id="5308380583665731573">接続</translation>
<translation id="7319983568955948908">共有をåœæ­¢</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. All Rights Reserved.</translation>
-<translation id="8038108135592087998">Android版Chromeリモートデスクトップã®åˆå›žãƒªãƒªãƒ¼ã‚¹</translation>
<translation id="6668065415969892472">PIN ã‚’æ›´æ–°ã—ã¾ã—ãŸã€‚</translation>
<translation id="4513946894732546136">フィードãƒãƒƒã‚¯</translation>
<translation id="4277736576214464567">アクセス コードãŒç„¡åŠ¹ã§ã™ã€‚ã‚‚ã†ä¸€åº¦å…¥åŠ›ã—ã¦ãã ã•ã„。</translation>
@@ -214,5 +218,6 @@ US英語以外ã®ã‚­ãƒ¼ãƒœãƒ¼ãƒ‰ã‚’使用ã—ã¦ã„るリモートパソコンã§
<translation id="1818475040640568770">パソコンãŒç™»éŒ²ã•ã‚Œã¦ã„ã¾ã›ã‚“。</translation>
<translation id="4394049700291259645">無効ã«ã™ã‚‹</translation>
<translation id="4156740505453712750">ã“ã®ãƒ‘ソコンã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’ä¿è­·ã™ã‚‹ã«ã¯ã€<ph name="BOLD_START"/>6 æ¡ä»¥ä¸Š<ph name="BOLD_END"/>ã® PIN ã‚’é¸æŠžã—ã¦ãã ã•ã„。ã“ã® PIN ã¯åˆ¥ã®å ´æ‰€ã‹ã‚‰æŽ¥ç¶šã™ã‚‹ã¨ãã«å¿…è¦ã¨ãªã‚Šã¾ã™ã€‚</translation>
+<translation id="6398765197997659313">全画é¢è¡¨ç¤ºã‚’終了</translation>
<translation id="3286521253923406898">Chromoting ホスト コントローラ</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_ko.xtb b/remoting/resources/remoting_strings_ko.xtb
index 959649d132..c47b7de705 100644
--- a/remoting/resources/remoting_strings_ko.xtb
+++ b/remoting/resources/remoting_strings_ko.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">Chrome ì›ê²© ë°ìŠ¤í¬í†±ì˜ 호환ë˜ì§€ 않는 ë²„ì „ì´ ê°ì§€ë˜ì—ˆìŠµë‹ˆë‹¤. 양쪽 ì»´í“¨í„°ì˜ Chromeê³¼ Chrome ì›ê²© ë°ìŠ¤í¬í†±ì´ ëª¨ë‘ ìµœì‹  버전ì¸ì§€ 확ì¸í•œ ë’¤ 다시 ì‹œë„í•´ 주세요.</translation>
<translation id="6998989275928107238">호스트</translation>
<translation id="406849768426631008">ì˜ìƒ 채팅 중 다른 사용ìžë¥¼ ë„와주려면 <ph name="LINK_BEGIN"/>Google í–‰ì•„ì›ƒì˜ ì›ê²© ë°ìŠ¤í¬í†±<ph name="LINK_END"/>ì„ ì‚¬ìš©í•´ë³´ì„¸ìš”.</translation>
+<translation id="906458777597946297">창 최대화</translation>
<translation id="5397086374758643919">Chrome ì›ê²© ë°ìŠ¤í¬í†± 호스트 제거 프로그램</translation>
<translation id="3258789396564295715">Chrome ì›ê²© ë°ìŠ¤í¬í†±ì„ 사용하여 ì»´í“¨í„°ì— ì•ˆì „í•˜ê²Œ 액세스할 수 있습니다.</translation>
<translation id="5070121137485264635">ì›ê²© 호스트는 타사 웹사ì´íŠ¸ì— 대한 ì¸ì¦ì´ 필요합니다. 계ì†í•˜ë ¤ë©´ ì´ ì£¼ì†Œì— ì•¡ì„¸ìŠ¤í•  수 있ë„ë¡ Chrome ì›ê²© ë°ìŠ¤í¬í†±ì— 추가 ê¶Œí•œì„ í—ˆìš©í•´ì•¼ 합니다.</translation>
<translation id="2124408767156847088">Android 기기ì—ì„œ ì»´í“¨í„°ì— ì•ˆì „í•˜ê²Œ 액세스하세요.</translation>
+<translation id="3194245623920924351">Chrome ì›ê²© ë°ìŠ¤í¬í†±</translation>
+<translation id="3649256019230929621">창 최소화</translation>
<translation id="4808503597364150972"><ph name="HOSTNAME"/>ì˜ PINì„ ìž…ë ¥í•˜ì„¸ìš”.</translation>
<translation id="7672203038394118626">ì»´í“¨í„°ì˜ ì›ê²© ì—°ê²°ì´ ì‚¬ìš©ì¤‘ì§€ë˜ì—ˆìŠµë‹ˆë‹¤.</translation>
<translation id="8244400547700556338">ìžì„¸ížˆ 알아보기</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">취소</translation>
<translation id="7782471917492991422">ì»´í“¨í„°ì˜ ì „ì› ê´€ë¦¬ ì„¤ì •ì„ í™•ì¸í•˜ê³  유휴 ìƒíƒœì¼ ë•Œ 절전 모드로 전환ë˜ë„ë¡ ì„¤ì •ë˜ì§€ 않았는지 확ì¸í•˜ì„¸ìš”.</translation>
<translation id="7665369617277396874">계정 추가</translation>
+<translation id="5925497314631808737">• 전체 화면/몰입형 모드를 구현했습니다.
+• ìž‘ì—… í‘œì‹œì¤„ì„ ìˆ¨ê¸°ëŠ” ë° ì‚¬ìš©ë˜ëŠ” ì•„ì´ì½˜ì´ 개선ë˜ì—ˆìŠµë‹ˆë‹¤.</translation>
<translation id="2707879711568641861">Chrome ì›ê²© ë°ìŠ¤í¬í†±ì— 필요한 ì¼ë¶€ 구성 요소가 누ë½ë˜ì—ˆìŠµë‹ˆë‹¤. 최신 ë²„ì „ì„ ì„¤ì¹˜í–ˆëŠ”ì§€ 확ì¸í•œ ë’¤ 다시 ì‹œë„í•´ 주세요.</translation>
+<translation id="1779766957982586368">창 닫기</translation>
<translation id="2499160551253595098">사용 통계 ë° ì˜¤ë¥˜ ë³´ê³ ì„œ ìˆ˜ì§‘ì„ í—ˆìš©í•˜ì—¬ Chrome ì›ê²© ë°ìŠ¤í¬í†± ê°œì„ ì— ì°¸ì—¬í•˜ê² ìŠµë‹ˆë‹¤.</translation>
<translation id="7868137160098754906">ì›ê²© ì»´í“¨í„°ì˜ PINì„ ìž…ë ¥í•˜ì„¸ìš”.</translation>
<translation id="677755392401385740">사용ìž(<ph name="HOST_USERNAME"/>)ì˜ í˜¸ìŠ¤íŠ¸ê°€ 시작ë˜ì—ˆìŠµë‹ˆë‹¤.</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">다른 ì»´í“¨í„°ì— ì›ê²© ì—°ê²°ì„ ì‚¬ìš©í•˜ë ¤ë©´ Chrome ì›ê²© ë°ìŠ¤í¬í†±ì„ 설치하고 '<ph name="BUTTON_NAME"/>' ë²„íŠ¼ì„ í´ë¦­í•˜ì„¸ìš”.</translation>
<translation id="7444276978508498879">ì—°ê²°ëœ í´ë¼ì´ì–¸íŠ¸: <ph name="CLIENT_USERNAME"/></translation>
<translation id="4913529628896049296">연결 대기 중...</translation>
-<translation id="5714695932513333758">ì—°ê²° 기ë¡</translation>
<translation id="811307782653349804">어디서든 ë‚´ ì»´í“¨í„°ì— ì•¡ì„¸ìŠ¤í•  수 있습니다.</translation>
<translation id="2939145106548231838">í˜¸ìŠ¤íŠ¸ì— ëŒ€í•œ ì¸ì¦</translation>
<translation id="2366718077645204424">í˜¸ìŠ¤íŠ¸ì— ì—°ê²°í•  수 없습니다. 사용 ì¤‘ì¸ ë„¤íŠ¸ì›Œí¬ì˜ 구성 ë•Œë¬¸ì¼ ìˆ˜ 있습니다.</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">ì—°ê²°</translation>
<translation id="7319983568955948908">공유 중지</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. All Rights Reserved.</translation>
-<translation id="8038108135592087998">Androidìš© Chrome ì›ê²© ë°ìŠ¤í¬í†±ì˜ 첫 번째 버전입니다.</translation>
<translation id="6668065415969892472">PINì´ ì—…ë°ì´íŠ¸ë˜ì—ˆìŠµë‹ˆë‹¤.</translation>
<translation id="4513946894732546136">문제 신고</translation>
<translation id="4277736576214464567">액세스 코드가 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. 다시 ì‹œë„í•´ 주세요.</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">등ë¡ëœ 컴퓨터가 없습니다.</translation>
<translation id="4394049700291259645">사용 중지</translation>
<translation id="4156740505453712750">액세스 ì œí•œì„ í†µí•´ ì´ ì»´í“¨í„°ë¥¼ 보호하려면 <ph name="BOLD_START"/>최소 6ìžë¦¬<ph name="BOLD_END"/>ì˜ PINì„ ì„ íƒí•˜ì„¸ìš”. ì´ PINì€ ë‹¤ë¥¸ 위치ì—ì„œ ì—°ê²°í•  ë•Œ 필요합니다.</translation>
+<translation id="6398765197997659313">전체화면 닫기</translation>
<translation id="3286521253923406898">Chromoting 호스트 컨트롤러</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_lt.xtb b/remoting/resources/remoting_strings_lt.xtb
index b184d255b8..2dec187d99 100644
--- a/remoting/resources/remoting_strings_lt.xtb
+++ b/remoting/resources/remoting_strings_lt.xtb
@@ -13,10 +13,13 @@ priegloba</translation>
<translation id="2801119484858626560">Aptikta nesuderinama „Chrome“ nuotolinio kompiuterio valdymo versija. Įsitikinkite, kad naudojate naujausios versijos „Chrome“ ir „Chrome“ nuotolinį kompiuterio valdymą abiejuose kompiuteriuose, ir bandykite dar kartą.</translation>
<translation id="6998989275928107238">Ryšys užmezgamas su</translation>
<translation id="406849768426631008">Norite padėti kam nors bendraudami su jais vaizdo pokalbyje? Išbandykite <ph name="LINK_BEGIN"/>nuotolinį kompiuterio valdymą „Google Hangout“<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Padidinti langÄ…</translation>
<translation id="5397086374758643919">„Chrome“ nuotolinio kompiuterio valdymo prieglobos pašalinimo priemonė</translation>
<translation id="3258789396564295715">Galite saugiai pasiekti kompiuterį naudodami „Chrome“ nuotolinį kompiuterio valdymą.</translation>
<translation id="5070121137485264635">NuotolinÄ— priegloba reikalauja autentifikuoti treÄiosios Å¡alies svetainÄ™. Jei norite tÄ™sti, turite suteikti „Chrome“ nuotolinio kompiuterio valdymo programai papildomas teises pasiekti šį adresÄ…</translation>
<translation id="2124408767156847088">Saugiai pasiekite kompiuterius „Android“ įrenginiu.</translation>
+<translation id="3194245623920924351">Nuotolinis kompiut. valdymas</translation>
+<translation id="3649256019230929621">Sumažinti langą</translation>
<translation id="4808503597364150972">Įveskite „<ph name="HOSTNAME"/>“ PIN kodą.</translation>
<translation id="7672203038394118626">Nuotolinis ryšys šiame kompiuteryje neleidžiamas.</translation>
<translation id="8244400547700556338">Sužinokite, kaip tai padaryti.</translation>
@@ -27,7 +30,10 @@ priegloba</translation>
<translation id="7658239707568436148">Atšaukti</translation>
<translation id="7782471917492991422">Patikrinkite kompiuterio galios tvarkymo nustatymus ir įsitikinkite, kad jis nėra sukonfigūruotas užmigti neveikos būsenos.</translation>
<translation id="7665369617277396874">PridÄ—ti paskyrÄ…</translation>
+<translation id="5925497314631808737">• Įdiegtas viso ekrano / įtraukiantysis režimas.
+• Patobulinta piktograma, skirta slėpti veiksmų juostą.</translation>
<translation id="2707879711568641861">Trūksta kai kurių „Chrome“ nuotoliniam kompiuterio valdymui reikalingų komponentų. Įsitikinkite, kad įdiegėte naujausią versiją, ir bandykite dar kartą.</translation>
+<translation id="1779766957982586368">Uždaryti langą</translation>
<translation id="2499160551253595098">LeidÄ™ mums kaupti naudojimo statistikÄ… ir strigÄių ataskaitas, padÄ—site tobulinti „Chrome“ nuotolinį kompiuterio valdymÄ….</translation>
<translation id="7868137160098754906">Įveskite nuotolinio kompiuterio PIN kodą.</translation>
<translation id="677755392401385740">PradÄ—ta naudotojo priegloba: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Jei reikia informacijos apie privatumą, žr. „Google“ privatumo politiką (
<translation id="4361728918881830843">Jei norite įgalinti nuotolinį ryšį su kitu kompiuteriu, jame įdiekite „Chrome“ nuotolinį kompiuterio valdymą ir spustelėkite „<ph name="BUTTON_NAME"/>“.</translation>
<translation id="7444276978508498879">Prijungtas klientas: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Laukiama ryšio...</translation>
-<translation id="5714695932513333758">Ryšių istorija</translation>
<translation id="811307782653349804">Pasiekite savo kompiuterį iš bet kur.</translation>
<translation id="2939145106548231838">Autentifikuoti pagal prieglobÄ…</translation>
<translation id="2366718077645204424">Neįmanoma pasiekti prieglobos. Taip greiÄiausiai nutiko dÄ—l naudojamo tinklo konfigÅ«racijos.</translation>
@@ -189,7 +194,6 @@ valdymo priegloba</translation>
<translation id="5308380583665731573">Prisijungti</translation>
<translation id="7319983568955948908">Stabdyti bendrinimÄ…</translation>
<translation id="6681800064886881394">Autorių teisės priklauso „Google Inc.“, 2013 m. Visos teisės saugomos.</translation>
-<translation id="8038108135592087998">Pirmoji „Android“ skirtos „Chrome“ nuotolinio kompiuterio valdymo programos laida</translation>
<translation id="6668065415969892472">PIN kodas atnaujintas.</translation>
<translation id="4513946894732546136">Atsiliepimai</translation>
<translation id="4277736576214464567">Netinkamas prieigos kodas. Bandykite dar kartÄ….</translation>
@@ -214,5 +218,6 @@ valdymo priegloba</translation>
<translation id="1818475040640568770">Neturite užregistruotų kompiuterių.</translation>
<translation id="4394049700291259645">Neleisti</translation>
<translation id="4156740505453712750">Jei norite apsaugoti prieigą prie šio kompiuterio, pasirinkite <ph name="BOLD_START"/>bent šešių skaitmenų<ph name="BOLD_END"/> PIN kodą. Šio PIN kodo reikės prisijungiant iš kitos vietos.</translation>
+<translation id="6398765197997659313">Išeiti iš viso ekrano režimo</translation>
<translation id="3286521253923406898">„Chrome“ nuotolinio ryšio prieglobos valdymo priemonė</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_lv.xtb b/remoting/resources/remoting_strings_lv.xtb
index 847a549026..5a8657aaa7 100644
--- a/remoting/resources/remoting_strings_lv.xtb
+++ b/remoting/resources/remoting_strings_lv.xtb
@@ -13,10 +13,13 @@ saimniekdators</translation>
<translation id="2801119484858626560">Tika konstatÄ“ta nesaderÄ«ga Chrome attÄlÄs darbvirsmas versija. PÄrbaudiet, vai abos datoros lietojat pÄrlÅ«ka Chrome un lietotnes Chrome attÄlÄ darbvirsma jaunÄko versiju, un mÄ“Ä£iniet vÄ“lreiz.</translation>
<translation id="6998989275928107238">Ar</translation>
<translation id="406849768426631008">Vai vÄ“laties palÄ«dzÄ“t kÄdam lietotÄjam video tÄ“rzÄ“Å¡anas sarunas laikÄ? IzmÄ“Ä£iniet <ph name="LINK_BEGIN"/>Google Hangouts lietotni AttÄlÄ darbvirsma<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksimizēt logu</translation>
<translation id="5397086374758643919">Chrome attÄlÄs darbvirsmas saimniekdatora atinstalÄ“Å¡anas programma</translation>
<translation id="3258789396564295715">Varat izveidot droÅ¡u savienojumu ar Å¡o datoru, izmantojot Chrome attÄlo darbvirsmu.</translation>
<translation id="5070121137485264635">AttÄlajÄ saimniekdatorÄ tiek pieprasÄ«ts, lai veicat autentificÄ“Å¡anu treÅ¡Äs puses vietnÄ“. Lai turpinÄtu, jums jÄpieÅ¡Ä·ir Chrome attÄlajai darbvirsmai papildu atļaujas piekļūt Å¡ai adresei:</translation>
<translation id="2124408767156847088">Droši piekļūstiet saviem datoriem no Android ierīces.</translation>
+<translation id="3194245623920924351">Chrome attÄlÄ darbvirsma</translation>
+<translation id="3649256019230929621">Minimizēt logu</translation>
<translation id="4808503597364150972">Lūdzu, ievadiet saimniekdatora <ph name="HOSTNAME"/> PIN.</translation>
<translation id="7672203038394118626">Å im datoram tika atspÄ“joti attÄlie savienojumi.</translation>
<translation id="8244400547700556338">Uzziniet vairÄk</translation>
@@ -27,7 +30,10 @@ saimniekdators</translation>
<translation id="7658239707568436148">Atcelt</translation>
<translation id="7782471917492991422">LÅ«dzu, pÄrbaudiet datora baroÅ¡anas pÄrvaldÄ«bas iestatÄ«jumus: datoram ir jÄbÅ«t konfigurÄ“tam tÄ, lai netiktu aktivizÄ“ts miega režīms, kad dators netiek lietots.</translation>
<translation id="7665369617277396874">Pievienot kontu</translation>
+<translation id="5925497314631808737">• Ieviest pilnekrÄna/iekļaujoÅ¡ais režīms.
+• Uzlabota darbību joslas slēpšanas ikona.</translation>
<translation id="2707879711568641861">TrÅ«kst dažu Chrome attÄlajai darbvirsmai nepiecieÅ¡amo komponentu. PÄrbaudiet, vai esat instalÄ“jis jaunÄko versiju, un mÄ“Ä£iniet vÄ“lreiz.</translation>
+<translation id="1779766957982586368">Aizvērt logu</translation>
<translation id="2499160551253595098">PalÄ«dziet mums uzlabot Chrome attÄlo darbvirsmu, ļaujot mums apkopot lietojuma statistiku un avÄriju pÄrskatus.</translation>
<translation id="7868137160098754906">LÅ«dzu, ievadiet attÄlÄ datora PIN.</translation>
<translation id="677755392401385740">Saimniekdators startÄ“ts Å¡Ädam lietotÄjam: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Lai saņemtu informÄciju par konfidencialitÄti, skatiet Google konfidencialitÄ
<translation id="4361728918881830843">Lai iespÄ“jotu attÄlus savienojumus ar citu datoru, instalÄ“jiet Chrome attÄlo darbvirsmu attiecÄ«gajÄ datorÄ un noklikÅ¡Ä·iniet uz <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Klients, kas izveidojis savienojumu: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">tiek gaidīta savienojuma izveide...</translation>
-<translation id="5714695932513333758">Savienojumu vēsture</translation>
<translation id="811307782653349804">Piekļūstiet savam datoram, atrodoties jebkurÄ vietÄ.</translation>
<translation id="2939145106548231838">Saimniekdatora autentificēšana</translation>
<translation id="2366718077645204424">Nevar sasniegt saimniekdatoru. IespÄ“jamais iemesls var bÅ«t tÄ«kla konfigurÄcija, kuru izmantojat.</translation>
@@ -189,7 +194,6 @@ darbvirsmas saimniekdators</translation>
<translation id="5308380583665731573">Savienot</translation>
<translation id="7319983568955948908">PÄrtraukt koplietoÅ¡anu</translation>
<translation id="6681800064886881394">Autortiesības © 2013 Google Inc. Visas tiesības paturētas.</translation>
-<translation id="8038108135592087998">Android ierÄ«cÄ“m paredzÄ“tÄs lietotnes “Chrome attÄlÄ darbvirsma†pirmÄ versija</translation>
<translation id="6668065415969892472">JÅ«su PIN ir atjauninÄts.</translation>
<translation id="4513946894732546136">Atsauksmes</translation>
<translation id="4277736576214464567">Piekļuves kods nav derīgs. Lūdzu, mēģiniet vēlreiz.</translation>
@@ -214,5 +218,6 @@ darbvirsmas saimniekdators</translation>
<translation id="1818475040640568770">Jums nav neviena reģistrēta datora.</translation>
<translation id="4394049700291259645">Atspējot</translation>
<translation id="4156740505453712750">Lai izveidotu droÅ¡u savienojumu ar Å¡o datoru, lÅ«dzu, izvÄ“lieties tÄdu PIN, kura garums ir <ph name="BOLD_START"/>vismaz seÅ¡i cipari<ph name="BOLD_END"/>. Å is PIN bÅ«s nepiecieÅ¡ams, lai izveidotu savienojumu no citas atraÅ¡anÄs vietas.</translation>
+<translation id="6398765197997659313">Iziet no pilnekrÄna režīma</translation>
<translation id="3286521253923406898">Chrome saites saimniekdatora kontrolleris</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_nl.xtb b/remoting/resources/remoting_strings_nl.xtb
index c4d99248d5..2269b7d127 100644
--- a/remoting/resources/remoting_strings_nl.xtb
+++ b/remoting/resources/remoting_strings_nl.xtb
@@ -13,10 +13,13 @@ host</translation>
<translation id="2801119484858626560">Er is een incompatibele versie van Chrome Remote Desktop gedetecteerd. Controleer of beide computers beschikken over de nieuwste versie van Chrome en Chrome Remote Desktop en probeer het opnieuw.</translation>
<translation id="6998989275928107238">Aan</translation>
<translation id="406849768426631008">Wil je tijdens een videochat iemand helpen? Probeer <ph name="LINK_BEGIN"/> Remote Desktop in Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Venster maximaliseren</translation>
<translation id="5397086374758643919">Verwijderprogramma voor Chrome Remote Desktop-host</translation>
<translation id="3258789396564295715">Je kunt veilig toegang krijgen tot deze computer via Chrome Remote Desktop.</translation>
<translation id="5070121137485264635">De externe host vereist dat je op een website van derden wordt geverifieerd. Als je wilt doorgaan, moet je Chrome Remote Desktop aanvullende machtigingen geven om toegang tot dit adres te krijgen:</translation>
<translation id="2124408767156847088">Veilig toegang tot je computers vanaf je Android-apparaat.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Venster minimaliseren</translation>
<translation id="4808503597364150972">Geef je pincode voor <ph name="HOSTNAME"/> op.</translation>
<translation id="7672203038394118626">Externe verbindingen voor deze computer zijn uitgeschakeld.</translation>
<translation id="8244400547700556338">Meer informatie.</translation>
@@ -27,7 +30,10 @@ host</translation>
<translation id="7658239707568436148">Annuleren</translation>
<translation id="7782471917492991422">Controleer de instellingen voor energiebeheer van je computer en zorg ervoor dat de slaapstand niet wordt geactiveerd wanneer de computer inactief is.</translation>
<translation id="7665369617277396874">Account toevoegen</translation>
+<translation id="5925497314631808737">• Volledig/uitgebreid scherm geïmplementeerd.
+• Pictogram voor het verbergen van de actiebalk verbeterd.</translation>
<translation id="2707879711568641861">Er ontbreken enkele componenten die vereist zijn voor Chrome Remote Desktop. Controleer of je de nieuwste versie hebt geïnstalleerd en probeer het opnieuw.</translation>
+<translation id="1779766957982586368">Venster sluiten</translation>
<translation id="2499160551253595098">Help ons Chrome Remote Desktop te verbeteren door ons toestemming te geven gebruiksstatistieken en crashrapporten te verzamelen.</translation>
<translation id="7868137160098754906">Voer je pincode voor de externe computer in.</translation>
<translation id="677755392401385740">Host gestart voor gebruiker: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Voor meer informatie over privacy bekijk je het Privacybeleid van Google (http:/
<translation id="4361728918881830843">Als je externe verbindingen met een andere computer mogelijk wilt maken, installeer je Chrome Remote Desktop op die computer en klik je op '<ph name="BUTTON_NAME"/>'.</translation>
<translation id="7444276978508498879">Client verbonden: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">wachten op verbinding…</translation>
-<translation id="5714695932513333758">Verbindingsgeschiedenis</translation>
<translation id="811307782653349804">Krijg overal toegang tot je eigen computer.</translation>
<translation id="2939145106548231838">Verifiëren naar host</translation>
<translation id="2366718077645204424">Kan de host niet bereiken. Dit komt waarschijnlijk door de configuratie van het netwerk dat je gebruikt.</translation>
@@ -189,7 +194,6 @@ Desktop-host</translation>
<translation id="5308380583665731573">Verbinding maken</translation>
<translation id="7319983568955948908">Delen stoppen</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Alle rechten voorbehouden.</translation>
-<translation id="8038108135592087998">Eerste release van Chrome Remote Desktop voor Android.</translation>
<translation id="6668065415969892472">Je pincode is bijgewerkt.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">De toegangscode is ongeldig. Probeer het opnieuw.</translation>
@@ -214,5 +218,6 @@ Desktop-host</translation>
<translation id="1818475040640568770">Je hebt geen computers geregistreerd.</translation>
<translation id="4394049700291259645">Uitschakelen</translation>
<translation id="4156740505453712750">Om de toegang tot deze computer te beschermen, kies je een pincode van <ph name="BOLD_START"/>ten minste zes cijfers<ph name="BOLD_END"/>. Je moet deze pincode opgeven wanneer je verbinding maakt vanaf een andere locatie.</translation>
+<translation id="6398765197997659313">Volledig scherm sluiten</translation>
<translation id="3286521253923406898">Controller voor Chromoting-host</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_no.xtb b/remoting/resources/remoting_strings_no.xtb
index 02cdd42914..85719ebe7b 100644
--- a/remoting/resources/remoting_strings_no.xtb
+++ b/remoting/resources/remoting_strings_no.xtb
@@ -13,10 +13,13 @@ Vert</translation>
<translation id="2801119484858626560">En ikke-kompatibel versjon av Chrome Eksternt skrivebord ble oppdaget. Kontrollér at du har den nyeste versjonen av Chrome og Chrome Eksternt skrivebord på begge maskinene, og prøv på nytt.</translation>
<translation id="6998989275928107238">Til</translation>
<translation id="406849768426631008">Har du lyst til å hjelpe noen mens du har en videosamtale med dem? Prøv <ph name="LINK_BEGIN"/>Eksternt skrivebord i Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksimerer vinduet</translation>
<translation id="5397086374758643919">Avinstalleringsprogram for Chrome Eksternt skrivebord-vert</translation>
<translation id="3258789396564295715">Du kan få sikker tilgang til denne datamaskinen ved å bruke Chrome Eksternt skrivebord.</translation>
<translation id="5070121137485264635">Den eksterne verten krever at du godkjenner via et tredjepartsnettsted. For å fortsette må du gi Chrome Eksternt skrivebord ytterligere tillatelser til å åpne denne adressen:</translation>
<translation id="2124408767156847088">FÃ¥ sikker tilgang til datamaskinene dine fra Android-enheten din.</translation>
+<translation id="3194245623920924351">Chrome Eksternt skrivebord</translation>
+<translation id="3649256019230929621">Minimerer vinduet</translation>
<translation id="4808503597364150972">Skriv inn PIN-koden for <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Eksterne tilkoblinger for denne datamaskinen har blitt deaktivert.</translation>
<translation id="8244400547700556338">Finn ut hvordan.</translation>
@@ -27,7 +30,10 @@ Vert</translation>
<translation id="7658239707568436148">Avbryt</translation>
<translation id="7782471917492991422">Kontrollér datamaskinens strøminnstillinger og sørg for at den ikke er konfigurert til å gå i dvale når den ikke er i bruk.</translation>
<translation id="7665369617277396874">Legg til konto</translation>
+<translation id="5925497314631808737">• implementert fullskjerm / maksimert modus
+• forbedret ikon for skjuling av handlingsraden</translation>
<translation id="2707879711568641861">Du mangler noen komponenter som kreves for Chrome Eksternt skrivebord. Kontrollér at du har installert den nyeste versjonen, og prøv på nytt.</translation>
+<translation id="1779766957982586368">Lukk vindu</translation>
<translation id="2499160551253595098">Hjelp oss med å forbedre Chrome Eksternt skrivebord ved å gi oss tillatelse til å samle bruksstatistikk og programstopprapporter.</translation>
<translation id="7868137160098754906">Skriv inn PIN-koden for den eksterne datamaskinen.</translation>
<translation id="677755392401385740">Vert startet for brukeren: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Du kan finne informasjon om personvern i Googles personvernregler (http://goo.gl
<translation id="4361728918881830843">For å muliggjøre ekstern tilkobling til en annen datamaskin må du installere Chrome Eksternt skrivebord på maskinen, og klikke på «<ph name="BUTTON_NAME"/>».</translation>
<translation id="7444276978508498879">Klienten ble tilknyttet: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">venter på tilkobling …</translation>
-<translation id="5714695932513333758">Tilkoblingslogg</translation>
<translation id="811307782653349804">Bruk din egen datamaskin uansett hvor du er.</translation>
<translation id="2939145106548231838">Autentiser for å være vert</translation>
<translation id="2366718077645204424">Kunne ikke nå verten. Dette er sannsynligvis på grunn av innstillingene for nettverket du bruker.</translation>
@@ -189,7 +194,6 @@ skrivebord-vert</translation>
<translation id="5308380583665731573">Koble til</translation>
<translation id="7319983568955948908">Stopp deling</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Med enerett.</translation>
-<translation id="8038108135592087998">Første versjon av Chrome Eksternt skrivebord for Android.</translation>
<translation id="6668065415969892472">PIN-koden din er oppdatert.</translation>
<translation id="4513946894732546136">Google Feedback</translation>
<translation id="4277736576214464567">Tilgangskoden er ugyldig. Prøv på nytt.</translation>
@@ -214,5 +218,6 @@ skrivebord-vert</translation>
<translation id="1818475040640568770">Du har ikke registrert noen datamaskiner.</translation>
<translation id="4394049700291259645">Deaktiver</translation>
<translation id="4156740505453712750">For å beskytte tilgangen til denne datamaskinen må du velge en PIN-kode bestående av <ph name="BOLD_START"/>minst seks tall<ph name="BOLD_END"/>. Denne PIN-koden kreves når man kobler til fra en annen posisjon.</translation>
+<translation id="6398765197997659313">Avslutt fullskjerm</translation>
<translation id="3286521253923406898">Kontroller for Chromoting-vert</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_pl.xtb b/remoting/resources/remoting_strings_pl.xtb
index 4de646818e..9264ad7274 100644
--- a/remoting/resources/remoting_strings_pl.xtb
+++ b/remoting/resources/remoting_strings_pl.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Wykryto niezgodną wersję Pulpitu zdalnego Chrome. Upewnij się, że masz najnowszą wersję Chrome oraz Pulpitu zdalnego Chrome na obu komputerach i spróbuj ponownie.</translation>
<translation id="6998989275928107238">Do</translation>
<translation id="406849768426631008">Chcesz komuś pomóc, jednocześnie rozmawiając z tą osobą przez czat wideo? Wypróbuj <ph name="LINK_BEGIN"/>Pulpit zdalny w Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maksymalizuj okno</translation>
<translation id="5397086374758643919">Program do odinstalowywania hosta Pulpitu zdalnego Chrome</translation>
<translation id="3258789396564295715">Możesz bezpiecznie korzystać z tego komputera przez Pulpit zdalny Chrome.</translation>
<translation id="5070121137485264635">Host zdalny wymaga uwierzytelnienia w witrynie innej firmy. Aby kontynuować, musisz udzielić Pulpitowi zdalnemu Chrome dodatkowych uprawnień dostępu do tego adresu:</translation>
<translation id="2124408767156847088">Uzyskaj bezpieczny dostęp do swoich komputerów z urządzenia z Androidem.</translation>
+<translation id="3194245623920924351">Pulpit zdalny Chrome</translation>
+<translation id="3649256019230929621">Minimalizuj okno</translation>
<translation id="4808503597364150972">Wpisz PIN dla: <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Połączenia zdalne z tym komputerem zostały wyłączone.</translation>
<translation id="8244400547700556338">Dowiedz siÄ™ jak.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Anuluj</translation>
<translation id="7782471917492991422">Sprawdź ustawienia zarządzania zasilaniem komputera i upewnij się, że nie jest on skonfigurowany do przechodzenia podczas bezczynności w tryb uśpienia.</translation>
<translation id="7665369617277396874">Dodaj konto</translation>
+<translation id="5925497314631808737">• Implementacja pełnego ekranu/trybu pojemnego.
+• Poprawiona ikona do ukrywania paska działania.</translation>
<translation id="2707879711568641861">Brak niektórych elementów wymaganych przez Pulpit zdalny Chrome. Upewnij się, że masz zainstalowaną najnowszą wersję i spróbuj ponownie.</translation>
+<translation id="1779766957982586368">Zamknij okno</translation>
<translation id="2499160551253595098">Pomóż nam udoskonalić Pulpit zdalny Chrome, zezwalając na gromadzenie przez nas statystyk użytkowania i raportów o awariach.</translation>
<translation id="7868137160098754906">Wpisz kod PIN komputera zdalnego.</translation>
<translation id="677755392401385740">Uruchomiono host dla użytkownika: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Informacje na temat prywatności znajdziesz w Polityce prywatności Google (http
<translation id="4361728918881830843">Aby umożliwić zdalne poÅ‚Ä…czenia z innym komputerem, zainstaluj na nim Pulpit zdalny Chrome i kliknij „<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Podłączył się klient: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">czekam na połączenie…</translation>
-<translation id="5714695932513333758">Historia połączeń</translation>
<translation id="811307782653349804">Dostęp do własnego komputera z dowolnego miejsca.</translation>
<translation id="2939145106548231838">Host wymaga uwierzytelnienia</translation>
<translation id="2366718077645204424">Nie można połączyć się z hostem. Problem prawdopodobnie wynika z konfiguracji Twojej sieci.</translation>
@@ -189,7 +194,6 @@ zdalnego Chrome</translation>
<translation id="5308380583665731573">Połącz się</translation>
<translation id="7319983568955948908">Zatrzymaj udostępnianie</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Wszelkie prawa zastrzeżone.</translation>
-<translation id="8038108135592087998">Pierwsza wersja Pulpitu zdalnego Chrome na Androida.</translation>
<translation id="6668065415969892472">PIN został zaktualizowany.</translation>
<translation id="4513946894732546136">Twoja opinia</translation>
<translation id="4277736576214464567">Kod dostępu jest nieprawidłowy. Spróbuj ponownie.</translation>
@@ -214,5 +218,6 @@ zdalnego Chrome</translation>
<translation id="1818475040640568770">Brak zarejestrowanych komputerów.</translation>
<translation id="4394049700291259645">Wyłącz</translation>
<translation id="4156740505453712750">Aby zabezpieczyć dostęp do tego komputera, wybierz PIN złożony z <ph name="BOLD_START"/>co najmniej sześciu cyfr<ph name="BOLD_END"/>. Będzie on wymagany przy łączeniu się z innej lokalizacji.</translation>
+<translation id="6398765197997659313">Zamknij pełny ekran</translation>
<translation id="3286521253923406898">Kontroler hosta funkcji Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_pt-BR.xtb b/remoting/resources/remoting_strings_pt-BR.xtb
index 176c0dbf25..c689188805 100644
--- a/remoting/resources/remoting_strings_pt-BR.xtb
+++ b/remoting/resources/remoting_strings_pt-BR.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Uma versão incompatível da Ãrea de trabalho remota do Google Chrome foi detectada. Certifique-se de que você possui a versão mais recente do Google Chrome e da Ãrea de trabalho remota do Google Chrome em ambos os computadores e tente novamente.</translation>
<translation id="6998989275928107238">Para</translation>
<translation id="406849768426631008">Quer ajudar alguém enquanto bate-papo por vídeo com essa pessoa? Use a <ph name="LINK_BEGIN"/>Ãrea de trabalho remota no Hangouts do Google<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximizar janela</translation>
<translation id="5397086374758643919">Desinstalador do host da Ãrea de trabalho remota do Google Chrome</translation>
<translation id="3258789396564295715">Você pode acessar este computador com segurança, usando a Ãrea de trabalho remota do Google Chrome.</translation>
<translation id="5070121137485264635">O host remoto requer a autenticação em um website de terceiros. Para continuar, você deve conceder à Ãrea de trabalho remota do Google Chrome acesso a permissões adicionais para acessar este endereço:</translation>
<translation id="2124408767156847088">Acesse com segurança seus computadores no seu dispositivo Android.</translation>
+<translation id="3194245623920924351">Ãrea de trabalho remota do Google Chrome</translation>
+<translation id="3649256019230929621">Minimizar janela</translation>
<translation id="4808503597364150972">Digite seu PIN para <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">As conexões remotas deste computador foram desativadas.</translation>
<translation id="8244400547700556338">Saiba como.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Cancelar</translation>
<translation id="7782471917492991422">Verifique as configurações de gerenciamento de energia de seu computador para garantir que ele não esteja configurado para entrar em modo de espera quando estiver inativo.</translation>
<translation id="7665369617277396874">Adicionar conta</translation>
+<translation id="5925497314631808737">• Implementado em tela cheia / modo imersivo.
+• Ãcone aprimorado para ocultar a barra de ação.</translation>
<translation id="2707879711568641861">Alguns componentes necessários à Ãrea de trabalho remota do Google Chrome estão faltando. Certifique-se de que esteja executando a última versão e tente novamente.</translation>
+<translation id="1779766957982586368">Fechar janela</translation>
<translation id="2499160551253595098">Ajude-nos a melhorar a Ãrea de trabalho remota do Google Chrome por meio da coleta de estatísticas de uso e de relatórios de falhas.</translation>
<translation id="7868137160098754906">Digite seu PIN do computador remoto.</translation>
<translation id="677755392401385740">Host iniciado para o usuário <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Para ver mais informações sobre privacidade, consulte a Política de Privacida
<translation id="4361728918881830843">Para ativar conexões remotas a um computador diferente, instale a Ãrea de trabalho remota do Google Chrome no computador desejado e clique em &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Cliente conectado: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">aguardando conexão...</translation>
-<translation id="5714695932513333758">Histórico de conexões</translation>
<translation id="811307782653349804">Acesse seu computador de qualquer lugar.</translation>
<translation id="2939145106548231838">Autenticar para host</translation>
<translation id="2366718077645204424">Não é possível acessar o host. Isso se deve, provavelmente, à configuração da rede que você está usando.</translation>
@@ -189,7 +194,6 @@ remota do Google Chrome</translation>
<translation id="5308380583665731573">Conectar</translation>
<translation id="7319983568955948908">Parar compartilhamento</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Todos os direitos reservados.</translation>
-<translation id="8038108135592087998">Primeira versão do aplicativo Ãrea de trabalho remota do Google Chrome para Android.</translation>
<translation id="6668065415969892472">O PIN foi atualizado.</translation>
<translation id="4513946894732546136">Comentários</translation>
<translation id="4277736576214464567">Código de acesso inválido. Tente novamente.</translation>
@@ -214,5 +218,6 @@ remota do Google Chrome</translation>
<translation id="1818475040640568770">Você não possui computadores registrados.</translation>
<translation id="4394049700291259645">Desativar</translation>
<translation id="4156740505453712750">Para proteger o acesso a este computador, selecione um PIN de <ph name="BOLD_START"/>pelo menos seis dígitos<ph name="BOLD_END"/>. Este PIN será necessário para estabelecer conexão a partir de outro local.</translation>
+<translation id="6398765197997659313">Sair do modo tela cheia</translation>
<translation id="3286521253923406898">Controlador do host do Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_pt-PT.xtb b/remoting/resources/remoting_strings_pt-PT.xtb
index fd494455cd..cdd292ec25 100644
--- a/remoting/resources/remoting_strings_pt-PT.xtb
+++ b/remoting/resources/remoting_strings_pt-PT.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">Foi detetada uma versão não compatível do Ambiente de Trabalho Remoto do Chrome. Certifique-se de que tem a versão mais recente do Chrome e do Ambiente de Trabalho Remoto do Chrome em ambos os computadores e tente novamente.</translation>
<translation id="6998989275928107238">Para</translation>
<translation id="406849768426631008">Pretende ajudar uma pessoa enquanto conversa com ela no chat de vídeo? Experimente o <ph name="LINK_BEGIN"/> Ambiente de Trabalho Remoto no Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximizar janela</translation>
<translation id="5397086374758643919">Desinstalador do Anfitrião do Ambiente de Trabalho Remoto do Chrome</translation>
<translation id="3258789396564295715">Pode aceder com segurança ao computador utilizando o Ambiente de Trabalho Remoto do Chrome.</translation>
<translation id="5070121137485264635">O anfitrião remoto requer que efetue a autenticação num Website de terceiros. Para continuar, necessita de conceder permissões adicionais ao Ambiente de Trabalho Remoto do Chrome para aceder a este endereço:</translation>
<translation id="2124408767156847088">Aceda aos seus computadores em segurança a partir do seu dispositivo Android.</translation>
+<translation id="3194245623920924351">Ambiente de Trabalho Remoto</translation>
+<translation id="3649256019230929621">Minimizar janela</translation>
<translation id="4808503597364150972">Introduza o PIN para <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Foram desativadas as ligações remotas neste computador.</translation>
<translation id="8244400547700556338">Saiba como.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Cancelar</translation>
<translation id="7782471917492991422">Verifique as definições de gestão de energia do computador e certifique-se de que não está configurado para suspender quando inativo.</translation>
<translation id="7665369617277396874">Adicionar conta</translation>
+<translation id="5925497314631808737">• Modo imerso/de ecrã inteiro implementado.
+• Ãcone melhorado para ocultar a barra de ação.</translation>
<translation id="2707879711568641861">Faltam alguns componentes necessários ao Ambiente de Trabalho Remoto do Chrome. Certifique-se de que instalou a versão mais recente e tente novamente.</translation>
+<translation id="1779766957982586368">Fechar janela</translation>
<translation id="2499160551253595098">Ajude-nos a melhorar o Ambiente de Trabalho Remoto do Chrome, permitindo-nos recolher estatísticas de utilização e relatórios de falhas.</translation>
<translation id="7868137160098754906">Introduza o PIN para o computador remoto.</translation>
<translation id="677755392401385740">O anfitrião começou para o utilizador: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Para mais informações acerca da privacidade, consulte a Política de Privacida
<translation id="4361728918881830843">Para ativar as ligações remotas a outro computador, instale o Ambiente de Trabalho Remoto do Chrome aí e clique em &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Cliente ligado: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">a aguardar ligação...</translation>
-<translation id="5714695932513333758">Histórico de ligações</translation>
<translation id="811307782653349804">Aceda ao seu computador a partir de qualquer lugar.</translation>
<translation id="2939145106548231838">Autenticar para anfitrião</translation>
<translation id="2366718077645204424">Não é possível contactar o anfitrião. Provavelmente, isso deve-se à configuração da rede que está a utilizar.</translation>
@@ -189,7 +194,6 @@ Remoto do Chrome</translation>
<translation id="5308380583665731573">Ligar</translation>
<translation id="7319983568955948908">Terminar Partilha</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Todos os Direitos Reservados.</translation>
-<translation id="8038108135592087998">Primeira versão do Ambiente de Trabalho Remoto do Chrome para Android.</translation>
<translation id="6668065415969892472">O seu PIN foi atualizado.</translation>
<translation id="4513946894732546136">Comentários</translation>
<translation id="4277736576214464567">O código de acesso é inválido. Tente novamente.</translation>
@@ -214,5 +218,6 @@ Remoto do Chrome</translation>
<translation id="1818475040640568770">Não tem quaisquer computadores registados.</translation>
<translation id="4394049700291259645">Desactivar</translation>
<translation id="4156740505453712750">Para proteger o acesso a este computador, escolha um PIN de <ph name="BOLD_START"/>pelo menos seis dígitos<ph name="BOLD_END"/>. Este PIN será necessário quando ligar a partir de outra localização.</translation>
+<translation id="6398765197997659313">Sair do modo de ecrã inteiro</translation>
<translation id="3286521253923406898">Controlador do Anfitrião do Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_ro.xtb b/remoting/resources/remoting_strings_ro.xtb
index 1756df9927..ff20a41f66 100644
--- a/remoting/resources/remoting_strings_ro.xtb
+++ b/remoting/resources/remoting_strings_ro.xtb
@@ -13,10 +13,13 @@ Chromoting</translation>
<translation id="2801119484858626560">A fost detectată o versiune incompatibilă a aplicației Desktop la distanță Chrome. Asigurați-vă că aveți cea mai recentă versiune Chrome și Desktop la distanță Chrome pe ambele computere și încercați din nou.</translation>
<translation id="6998989275928107238">Către</translation>
<translation id="406849768426631008">Doriți să ajutați pe cineva, iar în același timp să și discutați pe chatul video? Încercați <ph name="LINK_BEGIN"/>Control desktop la distanță din Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximizați fereastra</translation>
<translation id="5397086374758643919">Program de dezinstalare a gazdei pentru Desktop la distanță Chrome</translation>
<translation id="3258789396564295715">Puteți accesa acest computer în siguranță utilizând Desktop la distanță Chrome.</translation>
<translation id="5070121137485264635">Gazda la distanță solicită să vă autentificați pe un site terță parte. Pentru a continua, trebuie să acordați permisiuni suplimentare aplicației Desktop la distanță Chrome pentru a accesa această adresă:</translation>
<translation id="2124408767156847088">Accesați în siguranță computerele dvs. de pe dispozitivul Android.</translation>
+<translation id="3194245623920924351">Desktop la distanță Chrome</translation>
+<translation id="3649256019230929621">Minimizați fereastra</translation>
<translation id="4808503597364150972">Introduceți codul PIN pentru <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Conexiunile la distanță pentru acest computer au fost dezactivate.</translation>
<translation id="8244400547700556338">Aflați cum.</translation>
@@ -27,7 +30,10 @@ Chromoting</translation>
<translation id="7658239707568436148">Anulaţi</translation>
<translation id="7782471917492991422">Verificați setările de gestionare a energiei pentru computerul dvs. și asigurați-vă că nu este configurat să treacă în modul inactiv când este în repaus.</translation>
<translation id="7665369617277396874">Adăugați un cont</translation>
+<translation id="5925497314631808737">• S-a implementat modul ecran complet/imersiv.
+• S-a îmbunătățit pictograma pentru ascunderea barei de acțiuni.</translation>
<translation id="2707879711568641861">Unele componente necesare pentru Desktop la distanță Chrome lipsesc. Asigurați-vă că ați instalat cea mai recentă versiune a aplicației și încercați din nou.</translation>
+<translation id="1779766957982586368">Închideți fereastra</translation>
<translation id="2499160551253595098">Ajutați-ne să îmbunătățim Desktop la distanță Chrome, permițându-ne să colectăm statistici de utilizare și rapoarte de blocare.</translation>
<translation id="7868137160098754906">Introduceți codul PIN pentru computerul la distanță.</translation>
<translation id="677755392401385740">Gazda a fost inițiată pentru utilizatorul: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Pentru informații privind confidențialitatea, accesați Politica de confidenț
<translation id="4361728918881830843">Pentru a activa conexiunile la distanță pentru un alt computer, instalaÈ›i Desktop la distanță Chrome pe acesta È™i daÈ›i clic pe „<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Client conectat: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">se așteaptă realizarea conexiunii...</translation>
-<translation id="5714695932513333758">Istoricul conexiunilor</translation>
<translation id="811307782653349804">Accesați-vă computerul de oriunde.</translation>
<translation id="2939145106548231838">Autentificare la gazdă</translation>
<translation id="2366718077645204424">Gazda nu poate fi accesată. Acest lucru este cauzat, probabil, de configurația rețelei pe care o utilizați.</translation>
@@ -189,7 +194,6 @@ la distanță Chrome</translation>
<translation id="5308380583665731573">Conectați-vă</translation>
<translation id="7319983568955948908">Opriți permiterea accesului</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Toate drepturile rezervate.</translation>
-<translation id="8038108135592087998">Prima versiune a aplicației Desktop la distanță Chrome pentru Android.</translation>
<translation id="6668065415969892472">Codul PIN a fost actualizat.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">Codul de acces este greșit. Încercați din nou.</translation>
@@ -214,5 +218,6 @@ la distanță Chrome</translation>
<translation id="1818475040640568770">Nu aveți niciun computer înregistrat.</translation>
<translation id="4394049700291259645">Dezactivați</translation>
<translation id="4156740505453712750">Pentru a proteja accesul la acest computer, alegeți un cod PIN format din <ph name="BOLD_START"/>cel puțin șase cifre<ph name="BOLD_END"/>. Acest cod PIN va fi necesar când vă conectați din altă locație.</translation>
+<translation id="6398765197997659313">Ieșiți din ecranul complet</translation>
<translation id="3286521253923406898">Controler gazdă Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_ru.xtb b/remoting/resources/remoting_strings_ru.xtb
index 02b5ce0c6c..006b58c9b5 100644
--- a/remoting/resources/remoting_strings_ru.xtb
+++ b/remoting/resources/remoting_strings_ru.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">Обнаружена неÑовмеÑÑ‚Ð¸Ð¼Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð¾Ð³Ð¾ рабочего Ñтола Chrome. УÑтановите на обоих компьютерах поÑледние верÑии Ñтого Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ браузера Chrome, а затем повторите попытку.</translation>
<translation id="6998989275928107238">ХоÑÑ‚</translation>
<translation id="406849768426631008">Ðужно получить доÑтуп к удаленному компьютеру, не Ð²Ñ‹ÐºÐ»ÑŽÑ‡Ð°Ñ Ð²Ð¸Ð´ÐµÐ¾Ñ‡Ð°Ñ‚? ВоÑпользуйтеÑÑŒ функцией <ph name="LINK_BEGIN"/>Ð£Ð´Ð°Ð»ÐµÐ½Ð½Ð°Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒ в Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Развернуть окно</translation>
<translation id="5397086374758643919">Приложение Chrome Remote Desktop Host Uninstaller</translation>
<translation id="3258789396564295715">Ð’Ñ‹ можете безопаÑно подключитьÑÑ Ðº Ñтому компьютеру Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ Удаленного рабочего Ñтола Chrome.</translation>
<translation id="5070121137485264635">Ð”Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð½Ð¾Ð³Ð¾ доÑтупа к Ñайту требуетÑÑ Ð¿Ñ€Ð¾Ð¹Ñ‚Ð¸ аутентификацию. Дайте Удаленному рабочему Ñтолу Chrome дополнительные Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾Ñтупа к Ñледующему адреÑу:</translation>
<translation id="2124408767156847088">БезопаÑный удаленный доÑтуп к компьютеру Ñ Android-уÑтройÑтва</translation>
+<translation id="3194245623920924351">Удаленный рабочий Ñтол Chrome</translation>
+<translation id="3649256019230929621">Свернуть окно</translation>
<translation id="4808503597364150972">Введите PIN-код Ð´Ð»Ñ Ñ…Ð¾Ñта <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Удаленные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ñтого компьютера запрещены.</translation>
<translation id="8244400547700556338">Подробнее...</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">Отмена</translation>
<translation id="7782471917492991422">Отключите в наÑтройках ÑÐ½ÐµÑ€Ð³Ð¾Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð»ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð¿ÑŒÑŽÑ‚ÐµÑ€Ð° функцию перехода в ÑпÑщий режим при отÑутÑтвии активноÑти.</translation>
<translation id="7665369617277396874">Добавить аккаунт</translation>
+<translation id="5925497314631808737">• ПолноÑкранный режим и режим комфортного проÑмотра.
+• Ðовый значок Ð´Ð»Ñ ÑÐ²Ð¾Ñ€Ð°Ñ‡Ð¸Ð²Ð°Ð½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ»Ð¸ дейÑтвий.</translation>
<translation id="2707879711568641861">ОтÑутÑтвуют некоторые компоненты, необходимые Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка Удаленного рабочего Ñтола Chrome. УÑтановите поÑледнюю верÑию Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¸ повторите попытку.</translation>
+<translation id="1779766957982586368">Закрыть окно</translation>
<translation id="2499160551253595098">Разрешить Google Ñобирать данные ÑтатиÑтики иÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ отчеты о ÑбоÑÑ… Ð´Ð»Ñ ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ñ‹ Удаленного рабочего Ñтола Chrome</translation>
<translation id="7868137160098754906">Введите PIN-код Ð´Ð»Ñ Ð´Ð¾Ñтупа к удаленному компьютеру.</translation>
<translation id="677755392401385740">Запущен хоÑÑ‚ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">Чтобы разрешить удаленные Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ð½Ð° другом компьютере, уÑтановите на него Удаленный рабочий Ñтол Chrome и нажмите кнопку &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Подключен клиент: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">ожидание подключениÑ…</translation>
-<translation id="5714695932513333758">ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ð¹</translation>
<translation id="811307782653349804">Получайте доÑтуп к Ñвоему компьютеру откуда угодно.</translation>
<translation id="2939145106548231838">ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° хоÑте</translation>
<translation id="2366718077645204424">Ðе удалоÑÑŒ ÑвÑзатьÑÑ Ñ Ñ…Ð¾Ñтом. ВероÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð°Â â€“ иÑпользуемые вами наÑтройки Ñети.</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">Подключение</translation>
<translation id="7319983568955948908">Закрыть доÑтуп</translation>
<translation id="6681800064886881394">© Google Inc., 2013. Ð’Ñе права защищены.</translation>
-<translation id="8038108135592087998">Первый выпуÑк Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ &quot;Удаленный рабочий Ñтол Chrome&quot; Ð´Ð»Ñ Android.</translation>
<translation id="6668065415969892472">PIN-код обновлен.</translation>
<translation id="4513946894732546136">Отзыв</translation>
<translation id="4277736576214464567">Код доÑтупа недейÑтвителен. Повторите попытку.</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">Ðет зарегиÑтрированных компьютеров.</translation>
<translation id="4394049700291259645">Отключить</translation>
<translation id="4156740505453712750">Чтобы защитить компьютер от неÑанкционированного доÑтупа, выберите PIN-код, Ñодержащий <ph name="BOLD_START"/>не менее шеÑти цифр<ph name="BOLD_END"/>. Его необходимо будет ввеÑти при удаленном подключении.</translation>
+<translation id="6398765197997659313">Обычный режим</translation>
<translation id="3286521253923406898">Контроллер хоÑта Пульта Chrome</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_sk.xtb b/remoting/resources/remoting_strings_sk.xtb
index cb9e7eea49..244513032e 100644
--- a/remoting/resources/remoting_strings_sk.xtb
+++ b/remoting/resources/remoting_strings_sk.xtb
@@ -13,10 +13,13 @@ Hostiteľ</translation>
<translation id="2801119484858626560">Bola zistená nekompatibilná verzia Vzdialenej plochy Chrome. Uistite sa, že máte najnovÅ¡iu verziu prehliadaÄa Chrome a Vzdialenej plochy Chrome na oboch poÄítaÄoch a skúste to znova.</translation>
<translation id="6998989275928107238">Komu</translation>
<translation id="406849768426631008">Chcete niekomu pomôcÅ¥ a súÄasne s ním viesÅ¥ videohovor? Vyskúšajte funkciu <ph name="LINK_BEGIN"/>Vzdialený poÄítaÄ v službe Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximalizovať okno</translation>
<translation id="5397086374758643919">Nástroj na odinštalovanie hostiteľa Vzdialenej plochy Chrome</translation>
<translation id="3258789396564295715">K tomuto poÄítaÄu môžete bezpeÄne pristupovaÅ¥ pomocou Vzdialenej plochy Chrome.</translation>
<translation id="5070121137485264635">Vzdialený hostiteľ od vás vyžaduje overenie na webových stránkach tretej strany. Ak chcete pokraÄovaÅ¥, musíte vzdialenej ploche Chrome udeliÅ¥ dodatoÄné oprávnenie na prístup k tejto adrese:</translation>
<translation id="2124408767156847088">Zaistite bezpeÄný prístup k svojim poÄítaÄom zo zariadenia s Androidom.</translation>
+<translation id="3194245623920924351">Vzdialená plocha Chrome</translation>
+<translation id="3649256019230929621">Minimalizovať okno</translation>
<translation id="4808503597364150972">Zadajte kód PIN pre poÄítaÄ <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Vzdialené pripojenia k tomuto poÄítaÄu boli zakázané.</translation>
<translation id="8244400547700556338">Ďalšie informácie.</translation>
@@ -27,7 +30,10 @@ Hostiteľ</translation>
<translation id="7658239707568436148">Zrušiť</translation>
<translation id="7782471917492991422">Skontrolujte nastavenia správy napájania svojho poÄítaÄa a uistite sa, že nie je nakonfigurovaný prechod do režimu spánku pri neÄinnosti.</translation>
<translation id="7665369617277396874">PridaÅ¥ úÄet</translation>
+<translation id="5925497314631808737">• Implementovaný režim celej obrazovky / imerzný režim.
+• Vylepšená ikona na skrývanie panela akcií.</translation>
<translation id="2707879711568641861">Niektoré komponenty potrebné pre Vzdialenú plochu Chrome chýbajú. Uistite sa, že ste nainštalovali najnovšiu verziu a skúste to znova.</translation>
+<translation id="1779766957982586368">Zavrieť okno</translation>
<translation id="2499160551253595098">Pomôžte nám vylepÅ¡iÅ¥ službu Vzdialená plocha Chrome tým, že nám umožníte zhromažÄovaÅ¥ Å¡tatistiky používania a správy o zlyhaní.</translation>
<translation id="7868137160098754906">Zadajte Äíslo PIN pre vzdialený poÄítaÄ.</translation>
<translation id="677755392401385740">Hostiteľ bol spustený pre používateľa: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Informácie o ochrane osobných údajov nájdete v Pravidlách ochrany osobných
<translation id="4361728918881830843">Ak chcete povoliÅ¥ vzdialené pripojenie k inému poÄítaÄu, nainÅ¡talujte na ňom Vzdialenú plochu Chrome a potom kliknite na tlaÄidlo <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Klient je pripojený: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Äaká sa na pripojenie...</translation>
-<translation id="5714695932513333758">História pripojení</translation>
<translation id="811307782653349804">Pristupujte k svojmu poÄítaÄu odkiaľkoľvek.</translation>
<translation id="2939145106548231838">Overenie na úÄely hostenia</translation>
<translation id="2366718077645204424">Hostiteľa sa nepodarilo nájsť. Pravdepodobne je to spôsobené konfiguráciou siete, ktorú používate.</translation>
@@ -189,7 +194,6 @@ Hostiteľ pracovnej plochy</translation>
<translation id="5308380583665731573">Pripojiť</translation>
<translation id="7319983568955948908">Prestať zdieľať</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Všetky práva vyhradené.</translation>
-<translation id="8038108135592087998">Prvá verzia aplikácie Vzdialená plocha Chrome pre Android.</translation>
<translation id="6668065415969892472">Váš kód PIN bol aktualizovaný.</translation>
<translation id="4513946894732546136">Spätná väzba</translation>
<translation id="4277736576214464567">Prístupový kód je neplatný. Skúste to znova.</translation>
@@ -214,5 +218,6 @@ Hostiteľ pracovnej plochy</translation>
<translation id="1818475040640568770">Nemáte zaregistrované žiadne poÄítaÄe.</translation>
<translation id="4394049700291259645">Zakázať</translation>
<translation id="4156740505453712750">Ak chcete ochrániÅ¥ tento poÄítaÄ pred neautorizovaným prístupom, nastavte si kód PIN obsahujúci <ph name="BOLD_START"/>najmenej Å¡esÅ¥ Äíslic<ph name="BOLD_END"/>. Tento kód PIN budete musieÅ¥ zadaÅ¥ pri pokuse o pripojenie z iného miesta.</translation>
+<translation id="6398765197997659313">UkonÄiÅ¥ zobrazenie na celú obrazovku</translation>
<translation id="3286521253923406898">OvládaÄ hostiteľa funkcie Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_sl.xtb b/remoting/resources/remoting_strings_sl.xtb
index c94b5193b2..89735092f6 100644
--- a/remoting/resources/remoting_strings_sl.xtb
+++ b/remoting/resources/remoting_strings_sl.xtb
@@ -13,10 +13,13 @@ povezovanje s Chromom</translation>
<translation id="2801119484858626560">Zaznana je bila nezdružljiva razliÄica Oddaljenega namizja za Chrome. Preverite, ali imate v obeh raÄunalnikih najnovejÅ¡i razliÄici Google Chroma in Oddaljenega namizja za Chrome, ter poskusite znova.</translation>
<translation id="6998989275928107238">Za</translation>
<translation id="406849768426631008">Želite nekomu pomagati in hkrati v živo prek videa klepetati z njimi? Preskusite <ph name="LINK_BEGIN"/> Oddaljeno namizje v klepetalnicah Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">PoveÄanje okna</translation>
<translation id="5397086374758643919">Odstranitveni program gostitelja za Oddaljeno namizje za Chrome</translation>
<translation id="3258789396564295715">Oddaljeno namizje za Chrome omogoÄa varen dostop do tega raÄunalnika.</translation>
<translation id="5070121137485264635">Oddaljeni gostitelj zahteva, da previte pristnost na spletnem mestu tretje osebe. Če želite nadaljevati, morate Oddaljenemu namizju za Chrome dodeliti dodatna dovoljenja za dostop do tega naslova:</translation>
<translation id="2124408767156847088">Varno dostopajte do raÄunalnikov iz naprave s sistemom Android.</translation>
+<translation id="3194245623920924351">Oddaljeno namizje za Chrome</translation>
+<translation id="3649256019230929621">Pomanjšanje olna</translation>
<translation id="4808503597364150972">Vnesite kodo PIN za gostitelja <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Oddaljene povezave za ta raÄunalnik so onemogoÄene.</translation>
<translation id="8244400547700556338">VeÄ o tem.</translation>
@@ -27,7 +30,10 @@ povezovanje s Chromom</translation>
<translation id="7658239707568436148">PrekliÄi</translation>
<translation id="7782471917492991422">Preverite nastavitve raÄunalnika za upravljanje porabe in zagotovite, da za Äas nedejavnosti ni doloÄeno stanje pripravljenosti.</translation>
<translation id="7665369617277396874">Dodaj raÄun</translation>
+<translation id="5925497314631808737">• Na voljo je celozaslonski/potopni naÄin.
+• Izboljšana ikona za skrivanje vrstice dejanj.</translation>
<translation id="2707879711568641861">Ni nekaterih delov, ki so zahtevani za Oddaljeno namizje za Chrome. Preverite, ali imate najnovejÅ¡o razliÄico Google Chroma in poskusite znova.</translation>
+<translation id="1779766957982586368">Zapre okno</translation>
<translation id="2499160551253595098">Pomagajte nam izboljÅ¡ati Oddaljeno namizje za Chrome, tako da nam dovolite zbiranje statistiÄnih podatkov o uporabi in poroÄil o zruÅ¡itvah.</translation>
<translation id="7868137160098754906">Vnesite PIN za oddaljeni raÄunalnik.</translation>
<translation id="677755392401385740">Gostitelj zagnan za uporabnika: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Za veÄ informacij o zasebnosti si oglejte Googlov pravilnik o zasebnosti (http:
<translation id="4361728918881830843">ÄŒe želite omogoÄiti oddaljene povezave z drugim raÄunalnikom, vanj namestite Oddaljeno namizje za Chrome in kliknite »<ph name="BUTTON_NAME"/>«.</translation>
<translation id="7444276978508498879">Odjemalec povezan: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Äakanje na povezavo ...</translation>
-<translation id="5714695932513333758">Zgodovina povezav</translation>
<translation id="811307782653349804">Do raÄunalnika lahko dostopate od kjer koli.</translation>
<translation id="2939145106548231838">Preverjanje pristnosti pri gostitelju</translation>
<translation id="2366718077645204424">Ni mogoÄe vzpostaviti povezave z gostiteljem; verjetno zaradi konfiguracije omrežja, ki ga uporabljate.</translation>
@@ -189,7 +194,6 @@ namizje za Chrome</translation>
<translation id="5308380583665731573">Vzpostavi povezavo</translation>
<translation id="7319983568955948908">PrekliÄi skupno rabo</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. Vse pravice pridržane.</translation>
-<translation id="8038108135592087998">Prva izdaja aplikacije Oddaljeno namizje za Chrome za Android.</translation>
<translation id="6668065415969892472">PIN je posodobljen.</translation>
<translation id="4513946894732546136">Povratne informacije</translation>
<translation id="4277736576214464567">Koda za dostop je neveljavna. Poskusite znova.</translation>
@@ -214,5 +218,6 @@ namizje za Chrome</translation>
<translation id="1818475040640568770">Nimate registriranih raÄunalnikov.</translation>
<translation id="4394049700291259645">OnemogoÄi</translation>
<translation id="4156740505453712750">ÄŒe želite zaÅ¡Äititi dostop do tega raÄunalnika, izberite vsaj <ph name="BOLD_START"/>Å¡estmestni<ph name="BOLD_END"/> PIN. Ta bo zahtevan pri povezovanju z drugega mesta.</translation>
+<translation id="6398765197997659313">Izhod iz celozaslonskega naÄina</translation>
<translation id="3286521253923406898">Krmilnik gostitelja za Oddaljeno povezovanje s Chromom</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_sr.xtb b/remoting/resources/remoting_strings_sr.xtb
index 196d92ea71..f6b49a46ec 100644
--- a/remoting/resources/remoting_strings_sr.xtb
+++ b/remoting/resources/remoting_strings_sr.xtb
@@ -12,10 +12,13 @@
<translation id="2801119484858626560">Откривена је некомпатибилна верзија Chrome удаљеног рачунара. Проверите да ли на оба рачунара имате најновију верзију Chrome-а и Chrome удаљеног рачунара и покушајте поново.</translation>
<translation id="6998989275928107238">Коме</translation>
<translation id="406849768426631008">Желите да помогнете некој оÑоби док учеÑтвујете у видео ћаÑкању Ñа њом? ИÑпробајте <ph name="LINK_BEGIN"/>Удаљени рачунар у Google Hangouts-у<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Повећавање прозора</translation>
<translation id="5397086374758643919">Ðлатка за деинÑталацију хоÑта за Chrome удаљени рачунар</translation>
<translation id="3258789396564295715">Овом рачунару можете безбедно да приÑтупите помоћу Chrome удаљеног рачунара.</translation>
<translation id="5070121137485264635">Удаљени хоÑÑ‚ захтева да потврдите аутентичноÑÑ‚ на веб-Ñајту треће Ñтране. Да биÑте наÑтавили, морате да дате Chrome удаљеном рачунару додатне дозволе за приÑтуп овој адреÑи:</translation>
<translation id="2124408767156847088">Безбедно приÑтупајте рачунарима Ñа Android уређаја.</translation>
+<translation id="3194245623920924351">Chrome удаљени рачунар</translation>
+<translation id="3649256019230929621">Смањивање прозора</translation>
<translation id="4808503597364150972">УнеÑите PIN за <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">ДаљинÑко повезивање за овај рачунар је онемогућено.</translation>
<translation id="8244400547700556338">Сазнајте како.</translation>
@@ -26,7 +29,10 @@
<translation id="7658239707568436148">Откажи</translation>
<translation id="7782471917492991422">Проверите подешавања за управљање напајањем рачунара и уверите Ñе да није конфигуриÑан тако да прелази у режим Ñпавања када је неактиван.</translation>
<translation id="7665369617277396874">Додајте налог</translation>
+<translation id="5925497314631808737">• Примењен је режим целог екрана/утапајући режим.
+• Побољшана је икона за Ñкривање траке за радње.</translation>
<translation id="2707879711568641861">ÐедоÑтају неке компоненте које Ñу потребне за Chrome удаљени рачунар. Проверите да ли Ñте инÑталирали најновију верзију и покушајте поново.</translation>
+<translation id="1779766957982586368">Затварање прозора</translation>
<translation id="2499160551253595098">Помозите нам да побољшамо Chrome удаљени рачунар тако што ћете нам дозволити да прикупљамо ÑтатиÑтику коришћења и извештаје о отказивању.</translation>
<translation id="7868137160098754906">УнеÑите PIN за удаљени рачунар.</translation>
<translation id="677755392401385740">ХоÑÑ‚ је покренут за кориÑника: <ph name="HOST_USERNAME"/>.</translation>
@@ -133,7 +139,6 @@
<translation id="4361728918881830843">Да биÑте омогућили даљинÑко повезивање Ñа неким другим рачунаром, инÑталирајте Chrome удаљени рачунар на њега и кликните на „<ph name="BUTTON_NAME"/>“.</translation>
<translation id="7444276978508498879">Повезани клијент: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">чека Ñе уÑпоÑтављање везе…</translation>
-<translation id="5714695932513333758">ИÑторија повезивања</translation>
<translation id="811307782653349804">ПриÑтупите ÑопÑтвеном рачунару Ñа било које локације.</translation>
<translation id="2939145106548231838">Потврдите аутентичноÑÑ‚ на хоÑту</translation>
<translation id="2366718077645204424">Ðије могуће уÑпоÑтавити везу Ñа хоÑтом. До овога је вероватно дошло због конфигурације мреже коју кориÑтите.</translation>
@@ -187,7 +192,6 @@
<translation id="5308380583665731573">Повежи Ñе</translation>
<translation id="7319983568955948908">ЗауÑтави дељење</translation>
<translation id="6681800064886881394">ÐуторÑка права 2013. Google Inc. Сва права Ñу задржана.</translation>
-<translation id="8038108135592087998">Прво издање Chrome удаљеног рачунара за Android.</translation>
<translation id="6668065415969892472">PIN је ажуриран.</translation>
<translation id="4513946894732546136">Повратне информације</translation>
<translation id="4277736576214464567">ПриÑтупни кôд је неважећи. Покушајте поново.</translation>
@@ -212,5 +216,6 @@
<translation id="1818475040640568770">ÐиÑте региÑтровали ниједан рачунар.</translation>
<translation id="4394049700291259645">Онемогући</translation>
<translation id="4156740505453712750">Да биÑте заштитили приÑтуп овом рачунару, изаберите PIN од <ph name="BOLD_START"/>најмање шеÑÑ‚ цифара<ph name="BOLD_END"/>. Тај PIN ће бити потребан при повезивању Ñа друге локације.</translation>
+<translation id="6398765197997659313">Изађи из режима целог екрана</translation>
<translation id="3286521253923406898">Контролер хоÑта за Chromoting</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_sv.xtb b/remoting/resources/remoting_strings_sv.xtb
index 9d4688aecc..3643fa978c 100644
--- a/remoting/resources/remoting_strings_sv.xtb
+++ b/remoting/resources/remoting_strings_sv.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">Du använder en inkompatibel version av Chrome Remote Desktop. Se till att den senaste versionen av Chrome och Chrome Remote Desktop har installerats på båda datorerna och försök igen.</translation>
<translation id="6998989275928107238">Till</translation>
<translation id="406849768426631008">Vill du hjälpa någon samtidigt som ni har kontakt genom en videochatt? Pröva <ph name="LINK_BEGIN"/>Fjärrskrivbord i Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Maximera fönstret</translation>
<translation id="5397086374758643919">Chrome Remote Desktop Host Uninstaller</translation>
<translation id="3258789396564295715">Du kan få säker åtkomst till den här datorn med Chrome Remote Desktop.</translation>
<translation id="5070121137485264635">Fjärrvärden kräver att du autentiserar via en tredje parts webbplats. Om du vill fortsätta måste du ge Chrome Remote Desktop ytterligare behörigheter att komma åt den här adressen:</translation>
<translation id="2124408767156847088">Få säker åtkomst till dina datorer från Android-enheten.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Minimera fönstret</translation>
<translation id="4808503597364150972">Ange din PIN-kod för <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Fjärranslutningar har inaktiverats för den här datorn.</translation>
<translation id="8244400547700556338">Läs mer.</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">Avbryt</translation>
<translation id="7782471917492991422">Kontrollera datorns inställningar för energisparfunktioner och se till att den inte försätts i viloläge vid inaktivitet.</translation>
<translation id="7665369617277396874">Lägg till konto</translation>
+<translation id="5925497314631808737">• Helskärmsläge har implementerats.
+• Bättre ikon för att dölja åtgärdsfältet.</translation>
<translation id="2707879711568641861">Vissa komponenter som krävs för Chrome Remote Desktop saknas. Kontrollera att du har installerat den senaste versionen och försök igen.</translation>
+<translation id="1779766957982586368">Stäng fönstret</translation>
<translation id="2499160551253595098">Hjälp oss att förbättra Chrome Remote Desktop genom att låta oss samla in användningsstatistik och felrapporter.</translation>
<translation id="7868137160098754906">Ange din pinkod för fjärrdatorn.</translation>
<translation id="677755392401385740">Värd startad för användaren: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Du hittar mer information om sekretess i Googles sekretesspolicy (http://goo.gl/
<translation id="4361728918881830843">Om du vill aktivera fjärranslutning till en annan dator installerar du Chrome Remote Desktop på den och klickar på <ph name="BUTTON_NAME"/>.</translation>
<translation id="7444276978508498879">Klient ansluten: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">väntar på anslutning ...</translation>
-<translation id="5714695932513333758">Anslutningshistorik</translation>
<translation id="811307782653349804">Få åtkomst till din egen dator var du än är.</translation>
<translation id="2939145106548231838">Autentisera till värd</translation>
<translation id="2366718077645204424">Det går inte att nå värden. Detta beror troligen på konfigurationen av nätverket som du använder.</translation>
@@ -189,7 +194,6 @@ Desktop Host</translation>
<translation id="5308380583665731573">Anslut</translation>
<translation id="7319983568955948908">Sluta dela</translation>
<translation id="6681800064886881394">Upphovsrätt 2013 Google Inc. Med ensamrätt.</translation>
-<translation id="8038108135592087998">Första versionen av Chrome Remote Desktop för Android.</translation>
<translation id="6668065415969892472">PIN-koden har uppdaterats.</translation>
<translation id="4513946894732546136">Feedback</translation>
<translation id="4277736576214464567">Koden är ogiltig. Försök igen.</translation>
@@ -214,5 +218,6 @@ Desktop Host</translation>
<translation id="1818475040640568770">Du har inga registrerade datorer.</translation>
<translation id="4394049700291259645">Inaktivera</translation>
<translation id="4156740505453712750">Om du vill skydda åtkomsten till den här datorn väljer du en PIN-kod som består av <ph name="BOLD_START"/>minst sex siffror<ph name="BOLD_END"/>. PIN-koden krävs när du ansluter från en annan plats.</translation>
+<translation id="6398765197997659313">Avsluta helskärmsläge</translation>
<translation id="3286521253923406898">Chromoting Host Controller</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_th.xtb b/remoting/resources/remoting_strings_th.xtb
index 8f34410865..6ed7754990 100644
--- a/remoting/resources/remoting_strings_th.xtb
+++ b/remoting/resources/remoting_strings_th.xtb
@@ -13,10 +13,13 @@ Host</translation>
<translation id="2801119484858626560">ตรวจพบรุ่นที่เข้าà¸à¸±à¸™à¹„ม่ได้ของ Chrome Remote Desktop โปรดตรวจสอบให้à¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¸„ุณมี Chrome à¹à¸¥à¸° Chrome Remote Desktop เวอร์ชันล่าสุดบนคอมพิวเตอร์ทั้งสองเครื่องà¹à¸¥à¸°à¸¥à¸­à¸‡à¸­à¸µà¸à¸„รั้ง</translation>
<translation id="6998989275928107238">ถึง</translation>
<translation id="406849768426631008">หาà¸à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸Šà¹ˆà¸§à¸¢à¸„นอื่นในขณะที่วิดีโอà¹à¸Šà¸—à¸à¸±à¸šà¸žà¸§à¸à¹€à¸‚า ลองใช้<ph name="LINK_BEGIN"/> &quot;เดสà¸à¹Œà¸—็อปเครื่องอื่น&quot; ใน Google à¹à¸®à¸‡à¹€à¸­à¸²à¸—์<ph name="LINK_END"/></translation>
+<translation id="906458777597946297">ขยายหน้าต่างเต็มหน้าจอ</translation>
<translation id="5397086374758643919">Chrome Remote Desktop Host Uninstaller</translation>
<translation id="3258789396564295715">คุณสามารถเข้าถึงคอมพิวเตอร์เครื่องนี้ได้อย่างปลอดภัยโดยใช้ Chrome Remote Desktop</translation>
<translation id="5070121137485264635">โฮสต์ระยะไà¸à¸¥à¸à¸³à¸«à¸™à¸”ให้คุณตรวจสอบสิทธิ์à¸à¸±à¸šà¹€à¸§à¹‡à¸šà¹„ซต์ของบุคคลที่สาม หาà¸à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸•à¹ˆà¸­ คุณต้องให้สิทธิ์อนุà¸à¸²à¸•à¹€à¸žà¸´à¹ˆà¸¡à¹€à¸•à¸´à¸¡à¹à¸à¹ˆ Chrome Remote Desktop เพื่อเข้าถึงที่อยู่นี้:</translation>
<translation id="2124408767156847088">เข้าถึงคอมพิวเตอร์ของคุณจาà¸à¸­à¸¸à¸›à¸à¸£à¸“์à¹à¸­à¸™à¸”รอยด์ได้อย่างปลอดภัย</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">ย่อหน้าต่าง</translation>
<translation id="4808503597364150972">โปรดป้อน PIN ของคุณสำหรับ <ph name="HOSTNAME"/></translation>
<translation id="7672203038394118626">à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­à¸£à¸°à¸¢à¸°à¹„à¸à¸¥à¸ªà¸³à¸«à¸£à¸±à¸šà¸„อมพิวเตอร์เครื่องนี้ถูà¸à¸›à¸´à¸”ใช้งานà¹à¸¥à¹‰à¸§</translation>
<translation id="8244400547700556338">เรียนรู้วิธีà¸à¸²à¸£</translation>
@@ -27,7 +30,10 @@ Host</translation>
<translation id="7658239707568436148">ยà¸à¹€à¸¥à¸´à¸</translation>
<translation id="7782471917492991422">โปรดตรวจสอบà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่าà¸à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸žà¸¥à¸±à¸‡à¸‡à¸²à¸™à¸‚องคอมพิวเตอร์ที่ใช้ à¹à¸¥à¸°à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¹ƒà¸«à¹‰à¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¹„ม่ได้à¸à¸³à¸«à¸™à¸”ค่าให้เข้าสู่โหมดสลีปเมื่อไม่มีà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™</translation>
<translation id="7665369617277396874">เพิ่มบัà¸à¸Šà¸µ</translation>
+<translation id="5925497314631808737">• ใช้โหมดเต็มหน้าจอ/โหมดใหà¸à¹ˆà¸žà¸´à¹€à¸¨à¸©
+• ปรับปรุงไอคอนสำหรับà¸à¸²à¸£à¸‹à¹ˆà¸­à¸™à¹à¸–บà¸à¸²à¸£à¸—ำงาน</translation>
<translation id="2707879711568641861">องค์ประà¸à¸­à¸šà¸—ี่จำเป็นบางอย่างสำหรับ Chrome Remote Desktop ขาดหายไป โปรดตรวจสอบให้à¹à¸™à¹ˆà¹ƒà¸ˆà¸§à¹ˆà¸²à¸„ุณติดตั้งเวอร์ชันล่าสุดà¹à¸¥à¹‰à¸§à¹à¸¥à¸°à¸¥à¸­à¸‡à¸­à¸µà¸à¸„รั้ง</translation>
+<translation id="1779766957982586368">ปิดหน้าต่าง</translation>
<translation id="2499160551253595098">ช่วยเราปรับปรุง Chrome Remote Desktop ให้ดีขึ้นด้วยà¸à¸²à¸£à¸­à¸™à¸¸à¸à¸²à¸•à¹ƒà¸«à¹‰à¹€à¸£à¸²à¸£à¸§à¸šà¸£à¸§à¸¡à¸ªà¸–ิติà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¹à¸¥à¸°à¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚้อขัดข้อง</translation>
<translation id="7868137160098754906">โปรดป้อน PIN สำหรับคอมพิวเตอร์ระยะไà¸à¸¥</translation>
<translation id="677755392401385740">โฮสต์เริ่มใช้งานสำหรับผู้ใช้: <ph name="HOST_USERNAME"/></translation>
@@ -134,7 +140,6 @@ Host</translation>
<translation id="4361728918881830843">ในà¸à¸²à¸£à¹€à¸›à¸´à¸”ใช้งานà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­à¸£à¸°à¸¢à¸°à¹„à¸à¸¥à¸à¸±à¸šà¸„อมพิวเตอร์เครื่องอื่น ให้ติดตั้ง Chrome Remote Desktop ที่คอมพิวเตอร์ที่ต้องà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¹à¸¥à¸°à¸„ลิภ&quot;<ph name="BUTTON_NAME"/>&quot;</translation>
<translation id="7444276978508498879">ไคลเอ็นต์ที่เชื่อมต่อ: <ph name="CLIENT_USERNAME"/></translation>
<translation id="4913529628896049296">à¸à¸³à¸¥à¸±à¸‡à¸£à¸­à¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­...</translation>
-<translation id="5714695932513333758">ประวัติà¸à¸²à¸£à¹€à¸Šà¸·à¹ˆà¸­à¸¡à¸•à¹ˆà¸­</translation>
<translation id="811307782653349804">เข้าถึงคอมพิวเตอร์ของคุณเองจาà¸à¸—ี่ใดà¸à¹‡à¹„ด้</translation>
<translation id="2939145106548231838">ตรวจสอบสิทธิ์ตามที่โฮสต์à¸à¸³à¸«à¸™à¸”</translation>
<translation id="2366718077645204424">ไม่สามารถเข้าถึงโฮสต์ อาจเป็นเพราะà¸à¸²à¸£à¸à¸³à¸«à¸™à¸”ค่าของเครือข่ายที่คุณà¸à¸³à¸¥à¸±à¸‡à¹ƒà¸Šà¹‰à¸­à¸¢à¸¹à¹ˆ</translation>
@@ -189,7 +194,6 @@ Desktop Host</translation>
<translation id="5308380583665731573">เชื่อมต่อ</translation>
<translation id="7319983568955948908">หยุดà¸à¸²à¸£à¹à¸Šà¸£à¹Œ</translation>
<translation id="6681800064886881394">ลิขสิทธิ์ 2013 Google Inc. สงวนลิขสิทธิ์</translation>
-<translation id="8038108135592087998">Chrome Remote Desktop รุ่นà¹à¸£à¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹à¸­à¸™à¸”รอยด์</translation>
<translation id="6668065415969892472">PIN ของคุณได้รับà¸à¸²à¸£à¸­à¸±à¸›à¹€à¸”ตà¹à¸¥à¹‰à¸§</translation>
<translation id="4513946894732546136">ความคิดเห็น</translation>
<translation id="4277736576214464567">รหัสà¸à¸²à¸£à¹€à¸‚้าถึงไม่ถูà¸à¸•à¹‰à¸­à¸‡ โปรดลองอีà¸à¸„รั้ง</translation>
@@ -214,5 +218,6 @@ Desktop Host</translation>
<translation id="1818475040640568770">คอมพิวเตอร์ของคุณยังไม่ได้ลงทะเบียน</translation>
<translation id="4394049700291259645">ปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™</translation>
<translation id="4156740505453712750">หาà¸à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸à¸²à¸£à¹€à¸‚้าถึงคอมพิวเตอร์เครื่องนี้ โปรดเลือภPIN <ph name="BOLD_START"/>อย่างน้อยหà¸à¸«à¸¥à¸±à¸<ph name="BOLD_END"/> โดยจะต้องป้อน PIN นี้เมื่อเชื่อมต่อจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸­à¸·à¹ˆà¸™</translation>
+<translation id="6398765197997659313">ออà¸à¸ˆà¸²à¸à¸à¸²à¸£à¹à¸ªà¸”งà¹à¸šà¸šà¹€à¸•à¹‡à¸¡à¸«à¸™à¹‰à¸²à¸ˆà¸­</translation>
<translation id="3286521253923406898">Chromoting Host Controller</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_tr.xtb b/remoting/resources/remoting_strings_tr.xtb
index 5a4b6dcd16..c890157fd8 100644
--- a/remoting/resources/remoting_strings_tr.xtb
+++ b/remoting/resources/remoting_strings_tr.xtb
@@ -13,10 +13,13 @@ Ana Makinesi</translation>
<translation id="2801119484858626560">Chrome Uzaktan Masaüstü uygulamasının uyumsuz bir sürümü algılandı. Lütfen, her iki bilgisayarda Google Chrome ve Chrome Uzaktan Masaüstü uygulamalarının son sürümlerinin bulunduğundan emin olun ve tekrar deneyin.</translation>
<translation id="6998989275928107238">Kime</translation>
<translation id="406849768426631008">Biriyle görüntülü sohbet yaparken aynı anda ona yardım etmek ister misiniz? <ph name="LINK_BEGIN"/>Google Hangouts'taki Uzaktan Masaüstü'nü<ph name="LINK_END"/> deneyin.</translation>
+<translation id="906458777597946297">Pencereyi ekranı kaplayacak şekilde büyüt</translation>
<translation id="5397086374758643919">Chrome Uzaktan Masaüstü Ana Makine Yüklemesini Kaldırma Programı</translation>
<translation id="3258789396564295715">Chrome Uzaktan Masaüstü uygulamasını kullanarak bu bilgisayara güvenli bir şekilde erişebilirsiniz.</translation>
<translation id="5070121137485264635">Uzak ana makine üçüncü taraf bir web sitesine kimlik doğrulama işlemi yapmanızı gerektiriyor. Devam etmek üzere şu adrese erişim için Chrome Uzaktan Masaüstü'ne ek izinler vermelisiniz:</translation>
<translation id="2124408767156847088">Android cihazınızdan bilgisayarlarınıza güvenle erişin.</translation>
+<translation id="3194245623920924351">Chrome Uzaktan Masaüstü</translation>
+<translation id="3649256019230929621">Pencereyi simge durumuna küçült</translation>
<translation id="4808503597364150972">Lütfen <ph name="HOSTNAME"/> için PIN'inizi girin.</translation>
<translation id="7672203038394118626">Bu bilgisayara uzaktan bağlantılar devre dışı bırakıldı.</translation>
<translation id="8244400547700556338">Nasıl yapıldığını öğrenin.</translation>
@@ -27,7 +30,10 @@ Ana Makinesi</translation>
<translation id="7658239707568436148">Ä°ptal</translation>
<translation id="7782471917492991422">Lütfen bilgisayarınızın güç yönetimi ayarlarını kontrol edin ve boşta kaldığında uyku moduna geçecek şekilde ayarlanmadığından emin olun.</translation>
<translation id="7665369617277396874">Hesap ekle</translation>
+<translation id="5925497314631808737">• Tam ekran / yoğun içerik modu uygulandı
+• İşlem çubuğunu gizleme simgesi iyileştirildi</translation>
<translation id="2707879711568641861">Chrome Uzaktan Masaüstü için gereken bazı bileşenler eksik. Lütfen son sürümü yüklediğinizden emin olun ve tekrar deneyin.</translation>
+<translation id="1779766957982586368">Pencereyi kapat</translation>
<translation id="2499160551253595098">Kullanım istatistiklerini ve kilitlenme raporlarını toplamamıza izin vererek Chrome Uzaktan Masaüstü'nü daha iyi hale getirmemize yardımcı olun.</translation>
<translation id="7868137160098754906">Lütfen uzak bilgisayara ilişkin PIN'inizi girin.</translation>
<translation id="677755392401385740">Ana makine bu kullanıcı için başlatıldı: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Gizlilikle ilgili daha fazla bilgi edinmek için lütfen Google Gizlilik Politik
<translation id="4361728918881830843">Başka bir bilgisayara uzaktan bağlantıları etkinleştirmek için Chrome Uzaktan Masaüstü'nü o bilgisayara yükleyin ve “<ph name="BUTTON_NAME"/>†düğmesini tıklayın.</translation>
<translation id="7444276978508498879">İstemci bağlandı: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">bağlantı bekleniyor...</translation>
-<translation id="5714695932513333758">Bağlantı geçmişi</translation>
<translation id="811307782653349804">Bilgisayarınıza her yerden erişin.</translation>
<translation id="2939145106548231838">Ana makineyle kimlik doğrulaması yap</translation>
<translation id="2366718077645204424">Ana bilgisayara erişilemiyor. Bu sorun, kullandığınız ağın yapılandırmasından kaynaklanıyor olabilir.</translation>
@@ -189,7 +194,6 @@ Masaüstü Ana Makinesi</translation>
<translation id="5308380583665731573">BaÄŸlan</translation>
<translation id="7319983568955948908">Paylaşmayı Durdur</translation>
<translation id="6681800064886881394">Telif Hakkı 2013 Google Inc. Tüm Hakları Saklıdır.</translation>
-<translation id="8038108135592087998">Android için Chrome Uzak Masaüstü uygulamasının ilk sürümü</translation>
<translation id="6668065415969892472">PIN'iniz güncellendi.</translation>
<translation id="4513946894732546136">Geri Bildirim</translation>
<translation id="4277736576214464567">Erişim kodu geçersiz. Lütfen tekrar deneyin.</translation>
@@ -214,5 +218,6 @@ Masaüstü Ana Makinesi</translation>
<translation id="1818475040640568770">Kayıtlı bilgisayarınız yok.</translation>
<translation id="4394049700291259645">Devre dışı bırak</translation>
<translation id="4156740505453712750">Bu bilgisayara erişimi korumak için lütfen <ph name="BOLD_START"/>en az altı basamaklı<ph name="BOLD_END"/> bir PIN seçin. Başka bir yerden bağlanırken bu PIN gerekecektir.</translation>
+<translation id="6398765197997659313">Tam ekrandan çık</translation>
<translation id="3286521253923406898">Chromoting Ana Makine Denetleyicisi</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_uk.xtb b/remoting/resources/remoting_strings_uk.xtb
index 2be23c69e8..7322ebe8c7 100644
--- a/remoting/resources/remoting_strings_uk.xtb
+++ b/remoting/resources/remoting_strings_uk.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">ВиÑвлено неÑуміÑну верÑÑ–ÑŽ програми Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome. ПереконайтеÑÑ, що на обох комп’ютерах уÑтановлено найновіші верÑÑ–Ñ— веб-переглÑдача Chrome та програми Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome, Ñ– повторіть Ñпробу.</translation>
<translation id="6998989275928107238">Кому</translation>
<translation id="406849768426631008">Хочете отримати доÑтуп до віддаленого комп’ютера, не закриваючи відеочат? Спробуйте функцію <ph name="LINK_BEGIN"/>Віддаленого робочого Ñтолу в Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Розгорнути вікно</translation>
<translation id="5397086374758643919">Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ…Ð¾Ñту Віддаленого ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome</translation>
<translation id="3258789396564295715">Ви можете отримати безпечний доÑтуп до цього комп’ютера за допомогою програми Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome.</translation>
<translation id="5070121137485264635">Віддалений хоÑÑ‚ вимагає, щоб ви автентифікувалиÑÑ Ð½Ð° веб-Ñайті третьої Ñторони. Щоб продовжити, програмі Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome потрібно надати додаткові дозволи Ð´Ð»Ñ Ð´Ð¾Ñтупу до цієї адреÑи:</translation>
<translation id="2124408767156847088">Отримуйте безпечний доÑтуп до Ñвоїх комп’ютерів із приÑтрою Android.</translation>
+<translation id="3194245623920924351">Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome</translation>
+<translation id="3649256019230929621">Згорнути вікно</translation>
<translation id="4808503597364150972">Введіть PIN-код Ð´Ð»Ñ Ñ…Ð¾Ñту <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Віддалені Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ комп’ютера вимкнено.</translation>
<translation id="8244400547700556338">Докладніше.</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">СкаÑувати</translation>
<translation id="7782471917492991422">Перевірте параметри ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¶Ð¸Ð²Ð»ÐµÐ½Ð½Ñм комп’ютера та переконайтеÑÑ, що його не налаштовано переходити в режим Ñну, Ñкщо він неактивний.</translation>
<translation id="7665369617277396874">Додати обліковий запиÑ</translation>
+<translation id="5925497314631808737">• Додано повноекранний / реаліÑтичний режим.
+• Покращено значок Ñ…Ð¾Ð²Ð°Ð½Ð½Ñ Ð¿Ð°Ð½ÐµÐ»Ñ– дій.</translation>
<translation id="2707879711568641861">ВідÑутні деÑкі компоненти, потрібні Ð´Ð»Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ програми Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome. ПереконайтеÑÑ, що вÑтановлено найновішу верÑÑ–ÑŽ, Ñ– повторіть Ñпробу.</translation>
+<translation id="1779766957982586368">Закрити вікно</translation>
<translation id="2499160551253595098">Допоможіть нам покращити програму Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome, дозволивши збирати ÑтатиÑтику викориÑÑ‚Ð°Ð½Ð½Ñ Ñ‚Ð° звіти про аварійне Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸.</translation>
<translation id="7868137160098754906">Введіть PIN-код Ð´Ð»Ñ Ð²Ñ–Ð´Ð´Ð°Ð»ÐµÐ½Ð¾Ð³Ð¾ комп’ютера.</translation>
<translation id="677755392401385740">Запущено хоÑÑ‚ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувача <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">Щоб увімкнути віддалені Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð· іншим комп’ютером, уÑтановіть на ньому програму Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome Ñ– натиÑніть кнопку &quot;<ph name="BUTTON_NAME"/>&quot;.</translation>
<translation id="7444276978508498879">Під’єднано клієнт <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð·â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ...</translation>
-<translation id="5714695932513333758">ІÑÑ‚Ð¾Ñ€Ñ–Ñ Ð·â€™Ñ”Ð´Ð½Ð°Ð½ÑŒ</translation>
<translation id="811307782653349804">Отримуйте доÑтуп до Ñвого комп’ютера з будь-Ñкого міÑцÑ.</translation>
<translation id="2939145106548231838">ÐÐ²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð½Ð° хоÑÑ‚Ñ–</translation>
<translation id="2366718077645204424">Ðе вдаєтьÑÑ Ð·Ð²â€™ÑзатиÑÑ Ð· хоÑтом. Можливо, це пов’Ñзано з конфігурацією мережі, Ñку ви викориÑтовуєте.</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">З'єднатиÑÑ</translation>
<translation id="7319983568955948908">СкаÑувати доÑтуп</translation>
<translation id="6681800064886881394">ÐвторÑьке право 2013 Google Inc. УÑÑ– права захищено.</translation>
-<translation id="8038108135592087998">Перший випуÑк додатка Віддалене ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Chrome Ð´Ð»Ñ ÐžÐ¡ Android.</translation>
<translation id="6668065415969892472">PIN-код оновлено.</translation>
<translation id="4513946894732546136">Відгуки</translation>
<translation id="4277736576214464567">ÐедійÑний код доÑтупу. Повторіть Ñпробу.</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” зареєÑтрованих комп’ютерів.</translation>
<translation id="4394049700291259645">Вимкнути</translation>
<translation id="4156740505453712750">Щоб захиÑтити доÑтуп до цього комп’ютера, виберіть PIN-код із <ph name="BOLD_START"/>принаймні шеÑти Ñимволів<ph name="BOLD_END"/>. Цей PIN-код потрібно вводити, щоб під’єднатиÑÑ Ð· іншого міÑцÑ.</translation>
+<translation id="6398765197997659313">Вийти з повноекранного режиму</translation>
<translation id="3286521253923406898">Контролер хоÑту Віддаленого доÑтупу ОС Chrome</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_vi.xtb b/remoting/resources/remoting_strings_vi.xtb
index d784d3071c..1c319569c0 100644
--- a/remoting/resources/remoting_strings_vi.xtb
+++ b/remoting/resources/remoting_strings_vi.xtb
@@ -13,10 +13,13 @@ trên Chrome</translation>
<translation id="2801119484858626560">Äã phát hiện ra phiên bản Chrome Remote Desktop không tÆ°Æ¡ng thích. Vui lòng đảm bảo rằng bạn đã cài đặt phiên bản Chrome và Chrome Remote Desktop má»›i nhất trên cả hai máy tính và thá»­ lại.</translation>
<translation id="6998989275928107238">Äến</translation>
<translation id="406849768426631008">Bạn có muốn trợ giúp ai đó trong khi trò chuyện video với hỠkhông? Hãy thử ứng dụng <ph name="LINK_BEGIN"/> Remote Desktop trong Google Hangouts<ph name="LINK_END"/>.</translation>
+<translation id="906458777597946297">Phóng to cửa sổ</translation>
<translation id="5397086374758643919">Trình gỡ cài đặt máy chủ Chrome Remote Desktop</translation>
<translation id="3258789396564295715">Bạn có thể truy cập an toàn vào máy tính này bằng Chrome Remote Desktop.</translation>
<translation id="5070121137485264635">Máy chủ lÆ°u trữ từ xa yêu cầu bạn xác thá»±c vá»›i má»™t trang web của bên thứ ba. Äể tiếp tục, bạn phải cấp cho Chrome Remote Desktop thêm quyá»n truy cập vào địa chỉ sau:</translation>
<translation id="2124408767156847088">Truy cập máy tính an toàn từ thiết bị Android của bạn.</translation>
+<translation id="3194245623920924351">Chrome Remote Desktop</translation>
+<translation id="3649256019230929621">Thu nhỠcửa sổ</translation>
<translation id="4808503597364150972">Vui lòng nhập mã PIN của bạn cho <ph name="HOSTNAME"/>.</translation>
<translation id="7672203038394118626">Kết nối từ xa cho máy tính này đã bị tắt.</translation>
<translation id="8244400547700556338">Tìm hiểu cách thức.</translation>
@@ -27,7 +30,10 @@ trên Chrome</translation>
<translation id="7658239707568436148">Hủy</translation>
<translation id="7782471917492991422">Vui lòng kiểm tra cài đặt quản lý nguồn máy tính của bạn và đảm bảo rằng máy tính không được định cấu hình để ngủ khi không hoạt động.</translation>
<translation id="7665369617277396874">Thêm tài khoản</translation>
+<translation id="5925497314631808737">• Chế độ toàn màn hình / chế độ chìm được triển khai.
+• Biểu tượng ẩn thanh tác vụ được cải thiện.</translation>
<translation id="2707879711568641861">Thiếu một số thành phần cần thiết cho Chrome Remote Desktop. Vui lòng đảm bảo rằng bạn đã cài đặt phiên bản mới nhất và thử lại.</translation>
+<translation id="1779766957982586368">Äóng cá»­a sổ</translation>
<translation id="2499160551253595098">Giúp chúng tôi cải tiến Chrome Remote Desktop bằng cách cho phép chúng tôi thu thập số liệu thống kê sử dụng và báo cáo sự cố.</translation>
<translation id="7868137160098754906">Hãy nhập mã PIN của bạn cho máy tính từ xa.</translation>
<translation id="677755392401385740">Máy chủ đã khởi Ä‘á»™ng cho ngÆ°á»i dùng: <ph name="HOST_USERNAME"/>.</translation>
@@ -134,7 +140,6 @@ Các máy tính từ xa có bàn phím không phải tiếng Anh-Mỹ có thể
<translation id="4361728918881830843">Äể bật kết nối từ xa vá»›i má»™t máy tính khác, hãy cài đặt Chrome Remote Desktop trên máy tính đó và nhấp “<ph name="BUTTON_NAME"/>â€.</translation>
<translation id="7444276978508498879">Ứng dụng khách đã kết nối: <ph name="CLIENT_USERNAME"/>.</translation>
<translation id="4913529628896049296">đang đợi kết nối…</translation>
-<translation id="5714695932513333758">Lịch sử kết nối</translation>
<translation id="811307782653349804">Truy cập vào máy tính của chính bạn ở má»i nÆ¡i.</translation>
<translation id="2939145106548231838">Xác thực với máy chủ lưu trữ</translation>
<translation id="2366718077645204424">Không thể kết nối vá»›i máy chủ. Äiá»u này có thể do cấu hình của mạng bạn Ä‘ang sá»­ dụng.</translation>
@@ -189,7 +194,6 @@ Remote Desktop</translation>
<translation id="5308380583665731573">Kết nối</translation>
<translation id="7319983568955948908">Ngừng chia sẻ</translation>
<translation id="6681800064886881394">Bản quyá»n 2013 Google Inc. Má»i quyá»n được bảo lÆ°u.</translation>
-<translation id="8038108135592087998">Lần phát hành đầu tiên của Chrome Remote Desktop dành cho Android.</translation>
<translation id="6668065415969892472">Mã PIN của bạn đã được cập nhật.</translation>
<translation id="4513946894732546136">Phản hồi</translation>
<translation id="4277736576214464567">Mã truy cập không hợp lệ. Vui lòng thử lại.</translation>
@@ -214,5 +218,6 @@ Remote Desktop</translation>
<translation id="1818475040640568770">Bạn chưa đăng ký máy tính nào.</translation>
<translation id="4394049700291259645">Vô hiệu hóa</translation>
<translation id="4156740505453712750">Äể bảo vệ quyá»n truy cập vào máy tính này, vui lòng chá»n má»™t mã PIN <ph name="BOLD_START"/>ít nhất sáu chữ số<ph name="BOLD_END"/>. Mã PIN này sẽ được yêu cầu khi kết nối từ má»™t vị trí khác.</translation>
+<translation id="6398765197997659313">Thoát khá»i chế Ä‘á»™ toàn màn hình</translation>
<translation id="3286521253923406898">Trình Ä‘iá»u khiển máy chủ kết nối từ xa trên Chrome</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_zh-CN.xtb b/remoting/resources/remoting_strings_zh-CN.xtb
index a2bd0fa073..dbae24f647 100644
--- a/remoting/resources/remoting_strings_zh-CN.xtb
+++ b/remoting/resources/remoting_strings_zh-CN.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">系统检测到ä¸å…¼å®¹çš„ Chrome 远程桌é¢ç‰ˆæœ¬ã€‚请确ä¿ä¸¤å°è®¡ç®—机上使用的 Chrome å’Œ Chrome 远程桌é¢éƒ½æ˜¯æœ€æ–°ç‰ˆæœ¬ï¼Œç„¶åŽé‡è¯•ã€‚</translation>
<translation id="6998989275928107238">指å‘</translation>
<translation id="406849768426631008">想è¦é€šè¿‡è§†é¢‘èŠå¤©çš„æ–¹å¼ä¸ºå…¶ä»–用户æ供帮助å—?请å°è¯•ä½¿ç”¨ <ph name="LINK_BEGIN"/>Google 环èŠçš„远程桌é¢åŠŸèƒ½<ph name="LINK_END"/>。</translation>
+<translation id="906458777597946297">最大化窗å£</translation>
<translation id="5397086374758643919">Chrome 远程桌é¢ä¸»æœºå¸è½½ç¨‹åº</translation>
<translation id="3258789396564295715">您å¯ä½¿ç”¨ Chrome 远程桌é¢å®‰å…¨åœ°è®¿é—®æ­¤è®¡ç®—机。</translation>
<translation id="5070121137485264635">远程主机è¦æ±‚您对第三方网站进行身份验è¯ã€‚è¦ç»§ç»­æ“ä½œï¼Œæ‚¨å¿…é¡»å‘ Chrome 远程桌é¢æŽˆäºˆè®¿é—®ä»¥ä¸‹åœ°å€çš„é¢å¤–æƒé™ï¼š</translation>
<translation id="2124408767156847088">通过您的Android设备安全地访问自己的计算机。</translation>
+<translation id="3194245623920924351">Chrome远程桌é¢</translation>
+<translation id="3649256019230929621">最å°åŒ–窗å£</translation>
<translation id="4808503597364150972">请输入您用于“<ph name="HOSTNAME"/>â€çš„ PIN。</translation>
<translation id="7672203038394118626">此计算机的远程连接已åœç”¨ã€‚</translation>
<translation id="8244400547700556338">了解详情。</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">å–消</translation>
<translation id="7782471917492991422">请检查您的计算机电æºç®¡ç†è®¾ç½®ï¼Œç¡®ä¿æœªå°†è¯¥è®¾ç½®é…置为无æ“作时进入休眠模å¼ã€‚</translation>
<translation id="7665369617277396874">添加å¸æˆ·</translation>
+<translation id="5925497314631808737">• 采用了全å±/沉浸模å¼ã€‚
+• 改进了用于éšè—æ“作æ çš„图标。</translation>
<translation id="2707879711568641861">缺少 Chrome 远程桌é¢æ‰€éœ€çš„æŸäº›ç»„件。请确ä¿æ‚¨å·²å®‰è£…最新版本,然åŽé‡è¯•ã€‚</translation>
+<translation id="1779766957982586368">关闭窗å£</translation>
<translation id="2499160551253595098">å…许我们收集使用情况统计信æ¯å’Œå´©æºƒæŠ¥å‘Šï¼Œä»¥å¸®åŠ©æ”¹è¿› Chrome 远程桌é¢ã€‚</translation>
<translation id="7868137160098754906">请为远程计算机输入PINç ã€‚</translation>
<translation id="677755392401385740">已为用户<ph name="HOST_USERNAME"/>å¯åŠ¨ä¸»æœºã€‚</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">è¦å¯ç”¨åˆ°å¦ä¸€å°è®¡ç®—机的远程连接,请在该计算机上安装 Chrome 远程桌é¢å¹¶ç‚¹å‡»â€œ<ph name="BUTTON_NAME"/>â€ã€‚</translation>
<translation id="7444276978508498879">已连接客户端:<ph name="CLIENT_USERNAME"/>。</translation>
<translation id="4913529628896049296">正在等待建立连接…</translation>
-<translation id="5714695932513333758">连接历å²è®°å½•</translation>
<translation id="811307782653349804">从任何地方访问您自己的计算机。</translation>
<translation id="2939145106548231838">å‘主机验è¯èº«ä»½</translation>
<translation id="2366718077645204424">无法连接到主机,这å¯èƒ½æ˜¯ç”±æ‚¨æ‰€ä½¿ç”¨ç½‘络的é…置导致的。</translation>
@@ -163,7 +168,7 @@
<translation id="6527303717912515753">分享</translation>
<translation id="2926340305933667314">无法åœç”¨å¯¹æ­¤è®¡ç®—机的远程访问。请ç¨åŽé‡è¯•ã€‚</translation>
<translation id="6865175692670882333">查看/修改</translation>
-<translation id="5859141382851488196">新窗å£â€¦</translation>
+<translation id="5859141382851488196">打开新的窗å£â€¦</translation>
<translation id="2089514346391228378">此计算机的远程连接已å¯ç”¨ã€‚</translation>
<translation id="7693372326588366043">刷新主机列表</translation>
<translation id="8445362773033888690">在Google Play商店中查看</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">连接</translation>
<translation id="7319983568955948908">åœæ­¢å…±äº«</translation>
<translation id="6681800064886881394">版æƒæ‰€æœ‰2013 Google Inc。ä¿ç•™æ‰€æœ‰æƒåˆ©ã€‚</translation>
-<translation id="8038108135592087998">首次å‘布Android版Chrome远程桌é¢åº”用。</translation>
<translation id="6668065415969892472">您的 PIN 已更新。</translation>
<translation id="4513946894732546136">å馈</translation>
<translation id="4277736576214464567">访问代ç æ— æ•ˆï¼Œè¯·é‡è¯•ã€‚</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">您未注册任何计算机。</translation>
<translation id="4394049700291259645">åœç”¨</translation>
<translation id="4156740505453712750">为防止他人擅自访问此计算机,请选用一个<ph name="BOLD_START"/>至少 6 ä½æ•°<ph name="BOLD_END"/>çš„ PIN。当从其他ä½ç½®è¿žæŽ¥æ—¶ï¼Œå°†éœ€è¦è¾“入此 PIN。</translation>
+<translation id="6398765197997659313">退出全å±æ¨¡å¼</translation>
<translation id="3286521253923406898">Chrome 远程访问主机控制器</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/resources/remoting_strings_zh-TW.xtb b/remoting/resources/remoting_strings_zh-TW.xtb
index 0d5a8bf4dc..d350c596e2 100644
--- a/remoting/resources/remoting_strings_zh-TW.xtb
+++ b/remoting/resources/remoting_strings_zh-TW.xtb
@@ -13,10 +13,13 @@
<translation id="2801119484858626560">åµæ¸¬åˆ°ä¸ç›¸å®¹çš„ Chrome é ç«¯æ¡Œé¢ç‰ˆæœ¬ã€‚請確èªæ‚¨èˆ‡å…±ç”¨å°è±¡çš„電腦使用的都是最新版本的 Chrome å’Œ Chrome é ç«¯æ¡Œé¢ï¼Œç„¶å¾Œå†è©¦ä¸€æ¬¡ã€‚</translation>
<translation id="6998989275928107238">目標電腦</translation>
<translation id="406849768426631008">想è¦é€éŽè¦–訊通訊功能å”助其他使用者解決å•é¡Œå—Žï¼Ÿå»ºè­°æ‚¨è©¦è©¦ <ph name="LINK_BEGIN"/>Google Hangouts çš„é ç«¯æ¡Œé¢<ph name="LINK_END"/>。</translation>
+<translation id="906458777597946297">將視窗放到最大</translation>
<translation id="5397086374758643919">Chrome é ç«¯æ¡Œé¢ä¸»æ©Ÿç«¯è§£é™¤å®‰è£ç¨‹å¼</translation>
<translation id="3258789396564295715">您å¯ä»¥é€éŽ Chrome é ç«¯æ¡Œé¢å®‰å…¨åœ°å­˜å–這å°é›»è…¦ã€‚</translation>
<translation id="5070121137485264635">é ç«¯ä¸»æ©Ÿè¦æ±‚您å‘第三方網站進行驗證。如è¦ç¹¼çºŒï¼Œæ‚¨å¿…須授予é¡å¤–權é™ï¼Œè®“ Chrome é ç«¯æ¡Œé¢èƒ½å¤ å­˜å–這個ä½å€ï¼š</translation>
<translation id="2124408767156847088">é€éŽ Android è£ç½®å®‰å…¨å­˜å–您的電腦。</translation>
+<translation id="3194245623920924351">Chrome é ç«¯æ¡Œé¢</translation>
+<translation id="3649256019230929621">將視窗縮到最å°</translation>
<translation id="4808503597364150972">請輸入 <ph name="HOSTNAME"/> 的 PIN。</translation>
<translation id="7672203038394118626">這å°é›»è…¦çš„é ç«¯é€£ç·šå·²åœç”¨ã€‚</translation>
<translation id="8244400547700556338">瞭解詳情</translation>
@@ -27,7 +30,10 @@
<translation id="7658239707568436148">å–消</translation>
<translation id="7782471917492991422">請檢查電腦的電æºç®¡ç†è¨­å®šï¼Œç¢ºèªé›»è…¦åœ¨é–’置時ä¸æœƒé€²å…¥ä¼‘眠狀態。</translation>
<translation id="7665369617277396874">新增帳戶</translation>
+<translation id="5925497314631808737">• 實作全螢幕/沈浸模å¼ã€‚
+• 改善隱è—動作列圖示。</translation>
<translation id="2707879711568641861">找ä¸åˆ° Chrome é ç«¯æ¡Œé¢æ‰€éœ€çš„部分元件。請確èªæ‚¨å·²å®‰è£æœ€æ–°ç‰ˆæœ¬ï¼Œç„¶å¾Œå†è©¦ä¸€æ¬¡ã€‚</translation>
+<translation id="1779766957982586368">關閉視窗</translation>
<translation id="2499160551253595098">å…許我們收集使用統計資料和當機報告,å”助我們改善 Chrome é ç«¯æ¡Œé¢ã€‚</translation>
<translation id="7868137160098754906">請為é ç«¯é›»è…¦è¼¸å…¥æ‚¨çš„ PIN。</translation>
<translation id="677755392401385740">已為下列使用者啟動主機:<ph name="HOST_USERNAME"/>。</translation>
@@ -134,7 +140,6 @@
<translation id="4361728918881830843">如è¦ç‚ºå…¶ä»–電腦啟用é ç«¯é€£ç·šï¼Œè«‹åœ¨æ‚¨æƒ³è¦é€£ç·šçš„é›»è…¦ä¸Šå®‰è£ Chrome é ç«¯æ¡Œé¢ï¼Œç„¶å¾ŒæŒ‰ä¸€ä¸‹ [<ph name="BUTTON_NAME"/>]。</translation>
<translation id="7444276978508498879">已連線至用戶端:<ph name="CLIENT_USERNAME"/>。</translation>
<translation id="4913529628896049296">正在等待連線...</translation>
-<translation id="5714695932513333758">連線紀錄</translation>
<translation id="811307782653349804">無論您人在哪裡,都能存å–自己的電腦。</translation>
<translation id="2939145106548231838">主機驗證</translation>
<translation id="2366718077645204424">無法連線到主機,原因å¯èƒ½èˆ‡æ‚¨ä½¿ç”¨çš„網路設定有關。</translation>
@@ -163,7 +168,7 @@
<translation id="6527303717912515753">分享</translation>
<translation id="2926340305933667314">無法åœç”¨é€™å°é›»è…¦çš„é ç«¯å­˜å–功能,請ç¨å¾Œå†è©¦ã€‚</translation>
<translation id="6865175692670882333">檢視/編輯</translation>
-<translation id="5859141382851488196">開新視窗…</translation>
+<translation id="5859141382851488196">新增視窗…</translation>
<translation id="2089514346391228378">這å°é›»è…¦çš„é ç«¯é€£ç·šå·²å•Ÿç”¨ã€‚</translation>
<translation id="7693372326588366043">é‡æ–°æ•´ç†ä¸»æ©Ÿæ¸…å–®</translation>
<translation id="8445362773033888690">å‰å¾€ Google Play 商店查看</translation>
@@ -189,7 +194,6 @@
<translation id="5308380583665731573">連線</translation>
<translation id="7319983568955948908">åœæ­¢å…±ç”¨</translation>
<translation id="6681800064886881394">Copyright 2013 Google Inc. ä¿ç•™æ‰€æœ‰æ¬Šåˆ©ã€‚</translation>
-<translation id="8038108135592087998">Chrome é ç«¯æ¡Œé¢ Android 版 (第 1 版)。</translation>
<translation id="6668065415969892472">您的 PIN 已更新。</translation>
<translation id="4513946894732546136">æ„見回饋</translation>
<translation id="4277736576214464567">å­˜å–碼無效,請å†è©¦ä¸€æ¬¡ã€‚</translation>
@@ -214,5 +218,6 @@
<translation id="1818475040640568770">您尚未註冊任何電腦。</translation>
<translation id="4394049700291259645">åœç”¨</translation>
<translation id="4156740505453712750">如è¦ä¿è­·é€™å°é›»è…¦ï¼Œé¿å…ä¸æ˜Žäººå£«å…¥ä¾µï¼Œè«‹é¸æ“‡ä¸€å€‹<ph name="BOLD_START"/>至少 6 ä½æ•¸<ph name="BOLD_END"/>çš„ PIN。當使用者從其他ä½ç½®é€£ç·šæ™‚,必須先輸入這個 PIN 驗證身分。</translation>
+<translation id="6398765197997659313">退出全螢幕模å¼</translation>
<translation id="3286521253923406898">Chromoting 主機控制器</translation>
</translationbundle> \ No newline at end of file
diff --git a/remoting/tools/breakpad_tester_win.cc b/remoting/tools/breakpad_tester_win.cc
index b87d878bd1..4cd840573a 100644
--- a/remoting/tools/breakpad_tester_win.cc
+++ b/remoting/tools/breakpad_tester_win.cc
@@ -34,20 +34,21 @@ void usage(const char* program_name) {
} // namespace
int main(int argc, char** argv) {
- CommandLine::Init(argc, argv);
+ base::CommandLine::Init(argc, argv);
base::AtExitManager exit_manager;
remoting::InitHostLogging();
- const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ const base::CommandLine* command_line =
+ base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kHelpSwitchName) ||
command_line->HasSwitch(kQuestionSwitchName)) {
usage(argv[0]);
return kSuccessExitCode;
}
- CommandLine::StringVector args = command_line->GetArgs();
+ base::CommandLine::StringVector args = command_line->GetArgs();
if (args.size() != 1) {
usage(argv[0]);
return kUsageExitCode;
@@ -64,7 +65,7 @@ int main(int argc, char** argv) {
base::win::ScopedHandle process;
process.Set(OpenProcess(desired_access, FALSE, pid));
if (!process.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to open the process " << pid;
+ PLOG(ERROR) << "Failed to open the process " << pid;
return kErrorExitCode;
}
@@ -73,7 +74,7 @@ int main(int argc, char** argv) {
thread.Set(CreateRemoteThread(process.Get(), NULL, 0, NULL, NULL, 0,
&thread_id));
if (!thread.IsValid()) {
- LOG_GETLASTERROR(ERROR) << "Failed to create a remote thread in " << pid;
+ PLOG(ERROR) << "Failed to create a remote thread in " << pid;
return kErrorExitCode;
}
diff --git a/remoting/webapp/background.js b/remoting/webapp/background.js
index 118b7e5788..1976b91bbd 100644
--- a/remoting/webapp/background.js
+++ b/remoting/webapp/background.js
@@ -8,7 +8,8 @@ var kNewWindowId = 'new-window';
function createWindow() {
chrome.app.window.create('main.html', {
'width': 800,
- 'height': 600
+ 'height': 600,
+ 'frame': 'none'
});
};
diff --git a/remoting/webapp/base.js b/remoting/webapp/base.js
index f010d73442..88c3faa318 100644
--- a/remoting/webapp/base.js
+++ b/remoting/webapp/base.js
@@ -12,7 +12,7 @@
'use strict';
var base = {};
-base.debug = function () {};
+base.debug = function() {};
/**
* Whether to break in debugger and alert when an assertion fails.
@@ -104,7 +104,7 @@ base.doNothing = function() {};
* @param {!Object} dict
* @return {Array}
*/
-base.values = function (dict) {
+base.values = function(dict) {
return Object.keys(dict).map(
/** @param {string} key */
function(key) {
@@ -112,6 +112,20 @@ base.values = function (dict) {
});
};
+base.Promise = function() {};
+
+/**
+ * @param {number} delay
+ * @return {Promise} a Promise that will be fulfilled after |delay| ms.
+ */
+base.Promise.sleep = function(delay) {
+ return new Promise(
+ /** @param {function():void} fulfill */
+ function(fulfill) {
+ window.setTimeout(fulfill, delay);
+ });
+};
+
/**
* A mixin for classes with events.
*
diff --git a/remoting/webapp/browser_test/browser_test.js b/remoting/webapp/browser_test/browser_test.js
index 85985aa99f..786000291a 100644
--- a/remoting/webapp/browser_test/browser_test.js
+++ b/remoting/webapp/browser_test/browser_test.js
@@ -17,17 +17,17 @@
* method.
* For example:
*
- * browserTest.My_Test = function(myObjectLiteral) {};
- * browserTest.My_Test.prototype.run() = function() { ... };
+ * browserTest.My_Test = function() {};
+ * browserTest.My_Test.prototype.run(myObjectLiteral) = function() { ... };
*
* The browser test is async in nature. It will keep running until
* browserTest.fail("My error message.") or browserTest.pass() is called.
*
* For example:
*
- * browserTest.My_Test.prototype.run() = function() {
+ * browserTest.My_Test.prototype.run(myObjectLiteral) = function() {
* window.setTimeout(function() {
- * if (doSomething()) {
+ * if (doSomething(myObjectLiteral)) {
* browserTest.pass();
* } else {
* browserTest.fail('My error message.');
@@ -38,7 +38,7 @@
* You will then invoke the test in C++ by calling:
*
* RunJavaScriptTest(web_content, "My_Test", "{"
- * "pin: 123123"
+ * "pin: '123123'"
* "}");
*/
@@ -57,21 +57,28 @@ browserTest.init = function() {
if (result.succeeded) {
console.log('Test Passed.');
} else {
- console.error(result.error_message);
+ console.error('Test Failed.\n' +
+ result.error_message + '\n' + result.stack_trace);
}
}
};
};
-browserTest.assert = function(expr, message) {
+browserTest.expect = function(expr, message) {
if (!expr) {
message = (message) ? '<' + message + '>' : '';
- browserTest.fail('Assertion failed.' + message);
+ browserTest.fail('Expectation failed.' + message);
}
};
-browserTest.fail = function(error_message, opt_stack_trace) {
- var stack_trace = opt_stack_trace || base.debug.callstack();
+browserTest.fail = function(error) {
+ var error_message = error;
+ var stack_trace = base.debug.callstack();
+
+ if (error instanceof Error) {
+ error_message = error.toString();
+ stack_trace = error.stack;
+ }
// To run browserTest locally:
// 1. Go to |remoting_webapp_files| and look for
@@ -100,7 +107,7 @@ browserTest.pass = function() {
browserTest.clickOnControl = function(id) {
var element = document.getElementById(id);
- browserTest.assert(element);
+ browserTest.expect(element);
element.click();
};
@@ -110,45 +117,96 @@ browserTest.Timeout = {
DEFAULT: 5000
};
-browserTest.waitForUIMode = function(expectedMode, callback, opt_timeout) {
- var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
- var timerId = null;
-
- if (opt_timeout === undefined) {
- opt_timeout = browserTest.Timeout.DEFAULT;
+browserTest.onUIMode = function(expectedMode, opt_timeout) {
+ if (expectedMode == remoting.currentMode) {
+ // If the current mode is the same as the expected mode, return a fulfilled
+ // promise. For some reason, if we fulfill the promise in the same
+ // callstack, V8 will assert at V8RecursionScope.h(66) with
+ // ASSERT(!ScriptForbiddenScope::isScriptForbidden()).
+ // To avoid the assert, execute the callback in a different callstack.
+ return base.Promise.sleep(0);
}
- function onTimeout() {
- remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
- browserTest.fail('Timeout waiting for ' + expectedMode);
- }
+ return new Promise (function(fulfill, reject) {
+ var uiModeChanged = remoting.testEvents.Names.uiModeChanged;
+ var timerId = null;
+
+ if (opt_timeout === undefined) {
+ opt_timeout = browserTest.Timeout.DEFAULT;
+ }
- function onUIModeChanged (mode) {
- if (mode == expectedMode) {
+ function onTimeout() {
remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
- window.clearTimeout(timerId);
- timerId = null;
- try {
- callback();
- } catch (e) {
- browserTest.fail(e.toString(), e.stack);
+ reject('Timeout waiting for ' + expectedMode);
+ }
+
+ function onUIModeChanged(mode) {
+ if (mode == expectedMode) {
+ remoting.testEvents.removeEventListener(uiModeChanged, onUIModeChanged);
+ window.clearTimeout(timerId);
+ timerId = null;
+ fulfill();
}
}
- }
- if (opt_timeout != browserTest.Timeout.NONE) {
- timerId = window.setTimeout(onTimeout.bind(window, timerId), opt_timeout);
- }
- remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
+ if (opt_timeout != browserTest.Timeout.NONE) {
+ timerId = window.setTimeout(onTimeout, opt_timeout);
+ }
+ remoting.testEvents.addEventListener(uiModeChanged, onUIModeChanged);
+ });
+};
+
+browserTest.expectMe2MeError = function(errorTag) {
+ var AppMode = remoting.AppMode;
+ var Timeout = browserTest.Timeout;
+
+ var onConnected = browserTest.onUIMode(AppMode.IN_SESSION, Timeout.None);
+ var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME);
+
+ onConnected = onConnected.then(function() {
+ return Promise.reject(
+ 'Expected the Me2Me connection to fail.');
+ });
+
+ onFailure = onFailure.then(function() {
+ var errorDiv = document.getElementById('connect-error-message');
+ var actual = errorDiv.innerText;
+ var expected = l10n.getTranslationOrError(errorTag);
+
+ browserTest.clickOnControl('client-finished-me2me-button');
+
+ if (actual != expected) {
+ return Promise.reject('Unexpected failure. actual:' + actual +
+ ' expected:' + expected);
+ }
+ });
+
+ return Promise.race([onConnected, onFailure]);
+};
+
+browserTest.expectMe2MeConnected = function() {
+ var AppMode = remoting.AppMode;
+ // Timeout if the session is not connected within 30 seconds.
+ var SESSION_CONNECTION_TIMEOUT = 30000;
+ var onConnected = browserTest.onUIMode(AppMode.IN_SESSION,
+ SESSION_CONNECTION_TIMEOUT);
+ var onFailure = browserTest.onUIMode(AppMode.CLIENT_CONNECT_FAILED_ME2ME,
+ browserTest.Timeout.NONE);
+ onFailure = onFailure.then(function() {
+ var errorDiv = document.getElementById('connect-error-message');
+ var errorMsg = errorDiv.innerText;
+ return Promise.reject('Unexpected error - ' + errorMsg);
+ });
+ return Promise.race([onConnected, onFailure]);
};
browserTest.runTest = function(testClass, data) {
try {
- var test = new testClass(data);
- browserTest.assert(typeof test.run == 'function');
- test.run();
+ var test = new testClass();
+ browserTest.expect(typeof test.run == 'function');
+ test.run(data);
} catch (e) {
- browserTest.fail(e.toString(), e.stack);
+ browserTest.fail(e);
}
};
diff --git a/remoting/webapp/browser_test/cancel_pin_browser_test.js b/remoting/webapp/browser_test/cancel_pin_browser_test.js
new file mode 100644
index 0000000000..7ba01c761e
--- /dev/null
+++ b/remoting/webapp/browser_test/cancel_pin_browser_test.js
@@ -0,0 +1,46 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * @suppress {checkTypes}
+ * Browser test for the scenario below:
+ * 1. Attempt to connect.
+ * 2. Hit cancel at the PIN prompt.
+ * 3. Reconnect with the PIN.
+ * 4. Verify that the session is connected.
+ */
+
+'use strict';
+
+/** @constructor */
+browserTest.Cancel_PIN = function() {};
+
+browserTest.Cancel_PIN.prototype.run = function(data) {
+ browserTest.expect(typeof data.pin == 'string');
+
+ var AppMode = remoting.AppMode;
+ browserTest.clickOnControl('this-host-connect');
+ browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT).then(function() {
+ browserTest.clickOnControl('cancel-pin-entry-button');
+ return browserTest.onUIMode(AppMode.HOME);
+ }).then(function() {
+ browserTest.clickOnControl('this-host-connect');
+ return browserTest.onUIMode(AppMode.CLIENT_PIN_PROMPT);
+ }).then(
+ this.enterPin_.bind(this, data.pin)
+ ).then(function() {
+ // On fulfilled.
+ browserTest.pass();
+ }, function(reason) {
+ // On rejected.
+ browserTest.fail(reason);
+ });
+};
+
+browserTest.Cancel_PIN.prototype.enterPin_ = function(pin) {
+ document.getElementById('pin-entry').value = pin;
+ browserTest.clickOnControl('pin-connect-button');
+ return browserTest.expectMe2MeConnected();
+};
diff --git a/remoting/webapp/browser_test/invalid_pin_browser_test.js b/remoting/webapp/browser_test/invalid_pin_browser_test.js
new file mode 100644
index 0000000000..5cdea3e14a
--- /dev/null
+++ b/remoting/webapp/browser_test/invalid_pin_browser_test.js
@@ -0,0 +1,44 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * @suppress {checkTypes}
+ * Browser test for the scenario below:
+ * 1. Attempt to connect.
+ * 2. Enter |data.pin| at the PIN prompt.
+ * 3. Verify that there is connection error due to invalid access code.
+ */
+
+'use strict';
+
+/** @constructor */
+browserTest.Invalid_PIN = function() {};
+
+browserTest.Invalid_PIN.prototype.run = function(data) {
+ // Input validation.
+ browserTest.expect(typeof data.pin == 'string');
+
+ // Connect to me2me Host.
+ browserTest.clickOnControl('this-host-connect');
+
+ browserTest.onUIMode(remoting.AppMode.CLIENT_PIN_PROMPT).then(
+ this.enterPIN_.bind(this, data.pin)
+ ).then(
+ // Sleep for two seconds to allow the host backoff timer to reset.
+ base.Promise.sleep.bind(window, 2000)
+ ).then(function() {
+ // On fulfilled.
+ browserTest.pass();
+ }, function(reason) {
+ // On rejected.
+ browserTest.fail(reason);
+ });
+};
+
+browserTest.Invalid_PIN.prototype.enterPIN_ = function(pin) {
+ document.getElementById('pin-entry').value = pin;
+ browserTest.clickOnControl('pin-connect-button');
+ return browserTest.expectMe2MeError(remoting.Error.INVALID_ACCESS_CODE);
+};
diff --git a/remoting/webapp/browser_test/update_pin_browser_test.js b/remoting/webapp/browser_test/update_pin_browser_test.js
new file mode 100644
index 0000000000..0d0eba5fa4
--- /dev/null
+++ b/remoting/webapp/browser_test/update_pin_browser_test.js
@@ -0,0 +1,109 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * @suppress {checkTypes}
+ * Browser test for the scenario below:
+ * 1. Change the PIN.
+ * 2. Connect with the new PIN.
+ * 3. Verify the connection succeeded.
+ * 4. Disconnect and reconnect with the old PIN.
+ * 5. Verify the connection failed.
+ */
+
+'use strict';
+
+/** @constructor */
+browserTest.Update_PIN = function() {};
+
+browserTest.Update_PIN.prototype.run = function(data) {
+ var LOGIN_BACKOFF_WAIT = 2000;
+ // Input validation
+ browserTest.expect(typeof data.new_pin == 'string');
+ browserTest.expect(typeof data.old_pin == 'string');
+ browserTest.expect(data.new_pin != data.old_pin,
+ 'The new PIN and the old PIN cannot be the same');
+
+ this.changePIN_(data.new_pin).then(
+ this.connect_.bind(this)
+ ).then(
+ this.enterPIN_.bind(this, data.old_pin, true /* expectError*/)
+ ).then(
+ // Sleep for two seconds to allow for the login backoff logic to reset.
+ base.Promise.sleep.bind(null, LOGIN_BACKOFF_WAIT)
+ ).then(
+ this.connect_.bind(this)
+ ).then(
+ this.enterPIN_.bind(this, data.new_pin, false /* expectError*/)
+ ).then(
+ // Clean up the test by disconnecting and changing the PIN back
+ this.disconnect_.bind(this)
+ ).then(
+ // The PIN must be restored regardless of success or failure.
+ this.changePIN_.bind(this, data.old_pin),
+ this.changePIN_.bind(this, data.old_pin)
+ ).then(
+ // On fulfilled.
+ browserTest.pass,
+ // On rejected.
+ browserTest.fail
+ );
+};
+
+browserTest.Update_PIN.prototype.changePIN_ = function(newPin) {
+ var AppMode = remoting.AppMode;
+ var HOST_RESTART_WAIT = 10000;
+
+ browserTest.clickOnControl('change-daemon-pin');
+
+ return browserTest.onUIMode(AppMode.HOST_SETUP_ASK_PIN).then(function() {
+ var onSetupDone = browserTest.onUIMode(AppMode.HOST_SETUP_DONE);
+ document.getElementById('daemon-pin-entry').value = newPin;
+ document.getElementById('daemon-pin-confirm').value = newPin;
+ browserTest.clickOnControl('daemon-pin-ok');
+ return onSetupDone;
+ }).then(function() {
+ browserTest.clickOnControl('host-config-done-dismiss');
+ // On Linux, we restart the host after changing the PIN, need to sleep
+ // for ten seconds before the host is ready for connection.
+ return base.Promise.sleep(HOST_RESTART_WAIT);
+ });
+};
+
+browserTest.Update_PIN.prototype.connect_ = function() {
+ browserTest.clickOnControl('this-host-connect');
+ return browserTest.onUIMode(remoting.AppMode.CLIENT_PIN_PROMPT);
+};
+
+browserTest.Update_PIN.prototype.disconnect_ = function() {
+ var AppMode = remoting.AppMode;
+
+ remoting.disconnect();
+
+ return browserTest.onUIMode(AppMode.CLIENT_SESSION_FINISHED_ME2ME)
+ .then(function() {
+ var onHome = browserTest.onUIMode(AppMode.HOME);
+ browserTest.clickOnControl('client-finished-me2me-button');
+ return onHome;
+ });
+};
+
+browserTest.Update_PIN.prototype.enterPIN_ = function(pin, expectError) {
+ // Wait for 500ms before hitting the PIN button. From experiment, sometimes
+ // the PIN prompt does not dismiss without the timeout.
+ var CONNECT_PIN_WAIT = 500;
+
+ document.getElementById('pin-entry').value = pin;
+
+ return base.Promise.sleep(CONNECT_PIN_WAIT).then(function() {
+ browserTest.clickOnControl('pin-connect-button');
+ }).then(function() {
+ if (expectError) {
+ return browserTest.expectMe2MeError(remoting.Error.INVALID_ACCESS_CODE);
+ } else {
+ return browserTest.expectMe2MeConnected();
+ }
+ });
+};
diff --git a/remoting/webapp/build-webapp.py b/remoting/webapp/build-webapp.py
index 7f4a62c98b..01ffbe9fca 100755
--- a/remoting/webapp/build-webapp.py
+++ b/remoting/webapp/build-webapp.py
@@ -189,6 +189,11 @@ def buildWebApp(buildtype, version, mimetype, destination, zip_path,
findAndReplace(os.path.join(destination, 'plugin_settings.js'),
'HOST_PLUGIN_MIMETYPE', hostPluginMimeType)
+ # Set client plugin type.
+ client_plugin = 'pnacl' if webapp_type == 'v2_pnacl' else 'native'
+ findAndReplace(os.path.join(destination, 'plugin_settings.js'),
+ "'CLIENT_PLUGIN_TYPE'", "'" + client_plugin + "'")
+
# Allow host names for google services/apis to be overriden via env vars.
oauth2AccountsHost = os.environ.get(
'OAUTH2_ACCOUNTS_HOST', 'https://accounts.google.com')
diff --git a/remoting/webapp/client_plugin.js b/remoting/webapp/client_plugin.js
index 4252b40436..c2c15f2a6b 100644
--- a/remoting/webapp/client_plugin.js
+++ b/remoting/webapp/client_plugin.js
@@ -424,6 +424,12 @@ remoting.ClientPlugin.prototype.connect = function(
hostJid, hostPublicKey, localJid, sharedSecret,
authenticationMethods, authenticationTag,
clientPairingId, clientPairedSecret) {
+ var keyFilter = '';
+ if (navigator.platform.indexOf('Mac') == -1) {
+ keyFilter = 'mac';
+ } else if (navigator.userAgent.match(/\bCrOS\b/)) {
+ keyFilter = 'cros';
+ }
this.plugin.postMessage(JSON.stringify(
{ method: 'connect', data: {
hostJid: hostJid,
@@ -434,7 +440,8 @@ remoting.ClientPlugin.prototype.connect = function(
authenticationTag: authenticationTag,
capabilities: this.capabilities_.join(" "),
clientPairingId: clientPairingId,
- clientPairedSecret: clientPairedSecret
+ clientPairedSecret: clientPairedSecret,
+ keyFilter: keyFilter
}
}));
};
diff --git a/remoting/webapp/client_session.js b/remoting/webapp/client_session.js
index 7b0d80ecf4..12b46e5b10 100644
--- a/remoting/webapp/client_session.js
+++ b/remoting/webapp/client_session.js
@@ -176,28 +176,29 @@ remoting.ClientSession.prototype.updateScrollbarVisibility = function() {
if (!this.shrinkToFit_) {
// Determine whether or not horizontal or vertical scrollbars are
// required, taking into account their width.
- needsVerticalScroll = window.innerHeight < this.plugin_.desktopHeight;
- needsHorizontalScroll = window.innerWidth < this.plugin_.desktopWidth;
+ var clientArea = this.getClientArea_();
+ needsVerticalScroll = clientArea.height < this.plugin_.desktopHeight;
+ needsHorizontalScroll = clientArea.width < this.plugin_.desktopWidth;
var kScrollBarWidth = 16;
if (needsHorizontalScroll && !needsVerticalScroll) {
needsVerticalScroll =
- window.innerHeight - kScrollBarWidth < this.plugin_.desktopHeight;
+ clientArea.height - kScrollBarWidth < this.plugin_.desktopHeight;
} else if (!needsHorizontalScroll && needsVerticalScroll) {
needsHorizontalScroll =
- window.innerWidth - kScrollBarWidth < this.plugin_.desktopWidth;
+ clientArea.width - kScrollBarWidth < this.plugin_.desktopWidth;
}
}
- var htmlNode = /** @type {HTMLElement} */ (document.documentElement);
+ var scroller = document.getElementById('scroller');
if (needsHorizontalScroll) {
- htmlNode.classList.remove('no-horizontal-scroll');
+ scroller.classList.remove('no-horizontal-scroll');
} else {
- htmlNode.classList.add('no-horizontal-scroll');
+ scroller.classList.add('no-horizontal-scroll');
}
if (needsVerticalScroll) {
- htmlNode.classList.remove('no-vertical-scroll');
+ scroller.classList.remove('no-vertical-scroll');
} else {
- htmlNode.classList.add('no-vertical-scroll');
+ scroller.classList.add('no-vertical-scroll');
}
};
@@ -362,8 +363,17 @@ remoting.ClientSession.prototype.createClientPlugin_ =
document.createElement('embed');
plugin.id = id;
- plugin.src = 'about://none';
- plugin.type = 'application/vnd.chromium.remoting-viewer';
+ if (remoting.settings.CLIENT_PLUGIN_TYPE == 'pnacl') {
+ plugin.src = 'remoting_client_pnacl.nmf';
+ plugin.type = 'application/x-pnacl';
+ } else if (remoting.settings.CLIENT_PLUGIN_TYPE == 'nacl') {
+ plugin.src = 'remoting_client_nacl.nmf';
+ plugin.type = 'application/x-nacl';
+ } else {
+ plugin.src = 'about://none';
+ plugin.type = 'application/vnd.chromium.remoting-viewer';
+ }
+
plugin.width = 0;
plugin.height = 0;
plugin.tabIndex = 0; // Required, otherwise focus() doesn't work.
@@ -568,6 +578,9 @@ remoting.ClientSession.prototype.removePlugin = function() {
function() {
remoting.fullscreen.removeListener(listener);
});
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setConnected(false);
+ }
// Remove mediasource-rendering class from video-contained - this will also
// hide the <video> element.
@@ -766,9 +779,10 @@ remoting.ClientSession.prototype.onSetScreenMode_ = function(event) {
remoting.ClientSession.prototype.setScreenMode_ =
function(shrinkToFit, resizeToClient) {
if (resizeToClient && !this.resizeToClient_) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
}
// If enabling shrink, reset bump-scroll offsets.
@@ -953,13 +967,18 @@ remoting.ClientSession.prototype.onConnectionStatusUpdate_ =
this.setFocusHandlers_();
this.onDesktopSizeChanged_();
if (this.resizeToClient_) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
}
- // Start listening for full-screen related events.
+ // Activate full-screen related UX.
remoting.fullscreen.addListener(this.callOnFullScreenChanged_);
remoting.fullscreen.syncWithMaximize(true);
+ if (remoting.windowFrame) {
+ remoting.windowFrame.setConnected(true);
+ }
+
} else if (status == remoting.ClientSession.State.FAILED) {
switch (error) {
case remoting.ClientSession.ConnectionError.HOST_IS_OFFLINE:
@@ -1019,9 +1038,10 @@ remoting.ClientSession.prototype.onSetCapabilities_ = function(capabilities) {
this.capabilities_ = capabilities;
if (this.hasCapability_(
remoting.ClientSession.Capability.SEND_INITIAL_RESOLUTION)) {
- this.plugin_.notifyClientResolution(window.innerWidth,
- window.innerHeight,
- window.devicePixelRatio);
+ var clientArea = this.getClientArea_();
+ this.plugin_.notifyClientResolution(clientArea.width,
+ clientArea.height,
+ window.devicePixelRatio);
}
};
@@ -1080,10 +1100,11 @@ remoting.ClientSession.prototype.onResize = function() {
remoting.ClientSession.Capability.RATE_LIMIT_RESIZE_REQUESTS)) {
kResizeRateLimitMs = 250;
}
+ var clientArea = this.getClientArea_();
this.notifyClientResolutionTimer_ = window.setTimeout(
this.plugin_.notifyClientResolution.bind(this.plugin_,
- window.innerWidth,
- window.innerHeight,
+ clientArea.width,
+ clientArea.height,
window.devicePixelRatio),
kResizeRateLimitMs);
}
@@ -1148,8 +1169,7 @@ remoting.ClientSession.prototype.updateDimensions = function() {
return;
}
- var windowWidth = window.innerWidth;
- var windowHeight = window.innerHeight;
+ var clientArea = this.getClientArea_();
var desktopWidth = this.plugin_.desktopWidth;
var desktopHeight = this.plugin_.desktopHeight;
@@ -1174,8 +1194,9 @@ remoting.ClientSession.prototype.updateDimensions = function() {
if (this.shrinkToFit_) {
// Reduce the scale, if necessary, to fit the whole desktop in the window.
- var scaleFitWidth = Math.min(scale, 1.0 * windowWidth / desktopWidth);
- var scaleFitHeight = Math.min(scale, 1.0 * windowHeight / desktopHeight);
+ var scaleFitWidth = Math.min(scale, 1.0 * clientArea.width / desktopWidth);
+ var scaleFitHeight =
+ Math.min(scale, 1.0 * clientArea.height / desktopHeight);
scale = Math.min(scaleFitHeight, scaleFitWidth);
// If we're running full-screen then try to handle common side-by-side
@@ -1334,12 +1355,13 @@ remoting.ClientSession.prototype.scroll_ = function(dx, dy) {
};
var stopX = { stop: false };
+ var clientArea = this.getClientArea_();
style.marginLeft = adjustMargin(style.marginLeft, dx,
- window.innerWidth, plugin.clientWidth, stopX);
+ clientArea.width, plugin.clientWidth, stopX);
var stopY = { stop: false };
- style.marginTop = adjustMargin(style.marginTop, dy,
- window.innerHeight, plugin.clientHeight, stopY);
+ style.marginTop = adjustMargin(
+ style.marginTop, dy, clientArea.height, plugin.clientHeight, stopY);
return stopX.stop && stopY.stop;
};
@@ -1396,8 +1418,9 @@ remoting.ClientSession.prototype.onMouseMove_ = function(event) {
return 0;
};
- var dx = computeDelta(event.x, window.innerWidth);
- var dy = computeDelta(event.y, window.innerHeight);
+ var clientArea = this.getClientArea_();
+ var dx = computeDelta(event.x, clientArea.width);
+ var dy = computeDelta(event.y, clientArea.height);
if (dx != 0 || dy != 0) {
/** @type {remoting.ClientSession} */
@@ -1475,3 +1498,15 @@ remoting.ClientSession.prototype.createGnubbyAuthHandler_ = function() {
this.sendGnubbyAuthMessage({'type': 'control', 'option': 'auth-v1'});
}
};
+
+/**
+ * @return {{width: number, height: number}} The height of the window's client
+ * area. This differs between apps v1 and apps v2 due to the custom window
+ * borders used by the latter.
+ * @private
+ */
+remoting.ClientSession.prototype.getClientArea_ = function() {
+ return remoting.windowFrame ?
+ remoting.windowFrame.getClientArea() :
+ { 'width': window.innerWidth, 'height': window.innerHeight };
+}
diff --git a/remoting/webapp/event_handlers.js b/remoting/webapp/event_handlers.js
index bcbc2010ab..e864c2db3d 100644
--- a/remoting/webapp/event_handlers.js
+++ b/remoting/webapp/event_handlers.js
@@ -101,6 +101,12 @@ function onLoad() {
remoting.init();
window.addEventListener('resize', remoting.onResize, false);
+ // When a window goes full-screen, a resize event is triggered, but the
+ // Fullscreen.isActive call is not guaranteed to return true until the
+ // full-screen event is triggered. In apps v2, the size of the window's
+ // client area is calculated differently in full-screen mode, so register
+ // for both events.
+ remoting.fullscreen.addListener(remoting.onResize);
if (!remoting.isAppsV2) {
window.addEventListener('beforeunload', remoting.promptClose, false);
window.addEventListener('unload', remoting.disconnect, false);
diff --git a/remoting/webapp/fullscreen_v2.js b/remoting/webapp/fullscreen_v2.js
index 4e19f348c7..3aaaab16f3 100644
--- a/remoting/webapp/fullscreen_v2.js
+++ b/remoting/webapp/fullscreen_v2.js
@@ -102,6 +102,7 @@ remoting.FullscreenAppsV2.prototype.syncWithMaximize = function(sync) {
remoting.FullscreenAppsV2.prototype.onFullscreened_ = function() {
this.notifyCallbacksOnRestore_ = true;
this.eventSource_.raiseEvent(this.kEventName_, true);
+ document.body.classList.add('fullscreen');
};
remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
@@ -111,6 +112,7 @@ remoting.FullscreenAppsV2.prototype.onMaximized_ = function() {
};
remoting.FullscreenAppsV2.prototype.onRestored_ = function() {
+ document.body.classList.remove('fullscreen');
if (this.hookingWindowEvents_) {
this.activate(false);
}
diff --git a/remoting/webapp/host_controller.js b/remoting/webapp/host_controller.js
index 056cdd9eef..70cf69e1ca 100644
--- a/remoting/webapp/host_controller.js
+++ b/remoting/webapp/host_controller.js
@@ -12,6 +12,13 @@ remoting.HostController = function() {
this.hostDispatcher_ = this.createDispatcher_();
};
+/**
+ * @return {remoting.HostDispatcher}
+ */
+remoting.HostController.prototype.getDispatcher = function() {
+ return this.hostDispatcher_;
+};
+
// Note that the values in the enums below are copied from
// daemon_controller.h and must be kept in sync.
/** @enum {number} */
diff --git a/remoting/webapp/host_dispatcher.js b/remoting/webapp/host_dispatcher.js
index a5ea504ece..0aa76b6769 100644
--- a/remoting/webapp/host_dispatcher.js
+++ b/remoting/webapp/host_dispatcher.js
@@ -87,6 +87,13 @@ remoting.HostDispatcher.prototype.tryToInitialize_ = function() {
};
/**
+ * @return {remoting.HostPlugin}
+ */
+remoting.HostDispatcher.prototype.getNpapiHost = function() {
+ return this.npapiHost_;
+}
+
+/**
* @param {remoting.HostController.Feature} feature The feature to test for.
* @param {function(boolean):void} onDone
* @return {void}
@@ -327,8 +334,8 @@ remoting.HostDispatcher.prototype.getUsageStatsConsent =
};
/**
- * This function installs the chromoting host using the NPAPI plugin and should
- * only be called on Windows.
+ * This function installs the host using the NPAPI plugin and should only be
+ * called on Windows.
*
* @param {function(remoting.HostController.AsyncResult):void} onDone
* @param {function(remoting.Error):void} onError
diff --git a/remoting/webapp/host_install_dialog.js b/remoting/webapp/host_install_dialog.js
index f94cca6a58..37e5e6b7d4 100644
--- a/remoting/webapp/host_install_dialog.js
+++ b/remoting/webapp/host_install_dialog.js
@@ -27,8 +27,8 @@ remoting.HostInstallDialog = function() {
this.continueInstallButton_.disabled = false;
this.cancelInstallButton_.disabled = false;
- /** @param {remoting.HostController.AsyncResult} asyncResult @private*/
- this.onDoneHandler_ = function(asyncResult) {}
+ /** @private*/
+ this.onDoneHandler_ = function() {}
/** @param {remoting.Error} error @private */
this.onErrorHandler_ = function(error) {}
@@ -49,48 +49,41 @@ remoting.HostInstallDialog.hostDownloadUrls = {
/**
* Starts downloading host components and shows installation prompt.
*
- * @param {remoting.HostController} hostController Used to install the host on
- * Windows.
- * @param {function(remoting.HostController.AsyncResult):void} onDone Callback
- * called when user clicks Ok, presumably after installing the host. The
- * handler must verify that the host has been installed and call tryAgain()
- * otherwise.
+ * @param {function():void} onDone Callback called when user clicks Ok,
+ * presumably after installing the host. The handler must verify that the host
+ * has been installed and call tryAgain() otherwise.
* @param {function(remoting.Error):void} onError Callback called when user
* clicks Cancel button or there is some other unexpected error.
* @return {void}
*/
-remoting.HostInstallDialog.prototype.show = function(
- hostController, onDone, onError) {
- // On Windows, host installation is automatic (handled by the NPAPI plugin)
- // and we don't show the dialog. On Mac and Linux, we show the dialog and the
- // user is expected to manually install the host before clicking OK.
- // TODO (weitaosu): Make host installation automatic for IT2Me (like Me2Me) on
- // Windows. Currently hostController is always null for IT2Me.
- if (navigator.platform == 'Win32' && hostController != null) {
- hostController.installHost(onDone, onError);
+remoting.HostInstallDialog.prototype.show = function(onDone, onError) {
+ this.continueInstallButton_.addEventListener(
+ 'click', this.onOkClickedHandler_, false);
+ this.cancelInstallButton_.addEventListener(
+ 'click', this.onCancelClickedHandler_, false);
+ remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
+
+ var hostPackageUrl =
+ remoting.HostInstallDialog.hostDownloadUrls[navigator.platform];
+ if (hostPackageUrl === undefined) {
+ this.onErrorHandler_(remoting.Error.CANCELLED);
+ return;
+ }
+
+ // Start downloading the package.
+ if (remoting.isAppsV2) {
+ // TODO(jamiewalch): Use chrome.downloads when it is available to
+ // apps v2 (http://crbug.com/174046)
+ window.open(hostPackageUrl);
} else {
- this.continueInstallButton_.addEventListener(
- 'click', this.onOkClickedHandler_, false);
- this.cancelInstallButton_.addEventListener(
- 'click', this.onCancelClickedHandler_, false);
- remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
-
- var hostPackageUrl =
- remoting.HostInstallDialog.hostDownloadUrls[navigator.platform];
- if (hostPackageUrl === undefined) {
- this.onErrorHandler_(remoting.Error.CANCELLED);
- return;
- }
-
- // Start downloading the package.
window.location = hostPackageUrl;
+ }
- /** @type {function(remoting.HostController.AsyncResult):void} */
- this.onDoneHandler_ = onDone;
+ /** @type {function():void} */
+ this.onDoneHandler_ = onDone;
- /** @type {function(remoting.Error):void} */
- this.onErrorHandler_ = onError;
- }
+ /** @type {function(remoting.Error):void} */
+ this.onErrorHandler_ = onError;
}
/**
@@ -114,7 +107,7 @@ remoting.HostInstallDialog.prototype.onOkClicked_ = function() {
this.continueInstallButton_.disabled = true;
this.cancelInstallButton_.disabled = true;
- this.onDoneHandler_(remoting.HostController.AsyncResult.OK);
+ this.onDoneHandler_();
}
remoting.HostInstallDialog.prototype.onCancelClicked_ = function() {
@@ -134,4 +127,3 @@ remoting.HostInstallDialog.prototype.onRetryClicked_ = function() {
'click', this.onCancelClickedHandler_, false);
remoting.setMode(remoting.AppMode.HOST_INSTALL_PROMPT);
};
-
diff --git a/remoting/webapp/host_it2me_dispatcher.js b/remoting/webapp/host_it2me_dispatcher.js
index f2a7e90c14..3c2fce7762 100644
--- a/remoting/webapp/host_it2me_dispatcher.js
+++ b/remoting/webapp/host_it2me_dispatcher.js
@@ -56,6 +56,8 @@ remoting.HostIt2MeDispatcher.prototype.initialize =
function onNativeMessagingStarted() {
console.log('Native Messaging supported.');
+
+ that.npapiHost_ = null;
onDispatcherInitialized();
}
@@ -90,6 +92,20 @@ remoting.HostIt2MeDispatcher.prototype.onNativeMessagingError_ =
}
/**
+ * @return {boolean}
+ */
+remoting.HostIt2MeDispatcher.prototype.usingNpapi = function() {
+ return this.npapiHost_ != null;
+}
+
+/**
+ * @return {remoting.HostPlugin}
+ */
+remoting.HostIt2MeDispatcher.prototype.getNpapiHost = function() {
+ return this.npapiHost_;
+}
+
+/**
* @param {string} email The user's email address.
* @param {string} authServiceWithToken Concatenation of the auth service
* (e.g. oauth2) and the access token.
diff --git a/remoting/webapp/host_screen.js b/remoting/webapp/host_screen.js
index 45e9975bbf..285fa99585 100644
--- a/remoting/webapp/host_screen.js
+++ b/remoting/webapp/host_screen.js
@@ -38,15 +38,21 @@ remoting.tryShare = function() {
onDispatcherInitializationFailed);
}
- /** @return {remoting.HostPlugin} */
+ /** @return {remoting.HostPlugin} */
var createPluginForIt2Me = function() {
return remoting.createNpapiPlugin(
document.getElementById('host-plugin-container'));
}
var onDispatcherInitialized = function () {
- remoting.startHostUsingDispatcher_(hostDispatcher);
- }
+ if (hostDispatcher.usingNpapi()) {
+ hostInstallDialog = new remoting.HostInstallDialog();
+ hostInstallDialog.show(tryInitializeDispatcher, onInstallError);
+ } else {
+ // Host alrady installed.
+ remoting.startHostUsingDispatcher_(hostDispatcher);
+ }
+ };
/** @param {remoting.Error} error */
var onDispatcherInitializationFailed = function(error) {
@@ -55,29 +61,19 @@ remoting.tryShare = function() {
return;
}
- // If we failed to initialize dispatcher then prompt the user to install the
- // host.
+ // If we failed to initialize the dispatcher then prompt the user to install
+ // the host manually.
if (hostInstallDialog == null) {
- var onDone = function(asyncResult) {
- // TODO (weitaosu): Ignore asyncResult for now because it is not set
- // during manual host installation. We should fix it after switching
- // to automatic host installation on Windows.
- tryInitializeDispatcher();
- };
-
hostInstallDialog = new remoting.HostInstallDialog();
- (/** @type {remoting.HostInstallDialog} */ hostInstallDialog).show(
- null,
- onDone,
- onInstallPromptError);
+ hostInstallDialog.show(tryInitializeDispatcher, onInstallError);
} else {
- (/** @type {remoting.HostInstallDialog} */ hostInstallDialog).tryAgain();
+ hostInstallDialog.tryAgain();
}
- }
+ };
/** @param {remoting.Error} error */
- var onInstallPromptError = function(error) {
+ var onInstallError = function(error) {
if (error == remoting.Error.CANCELLED) {
remoting.setMode(remoting.AppMode.HOME);
} else {
diff --git a/remoting/webapp/host_setup_dialog.js b/remoting/webapp/host_setup_dialog.js
index 13717a2cad..4697c9008c 100644
--- a/remoting/webapp/host_setup_dialog.js
+++ b/remoting/webapp/host_setup_dialog.js
@@ -273,16 +273,6 @@ remoting.HostSetupDialog.prototype.startNewFlow_ = function(sequence) {
};
/**
- * @param {string} tag
- * @private
- */
-remoting.HostSetupDialog.prototype.showProcessingMessage_ = function(tag) {
- var messageDiv = document.getElementById('host-setup-processing-message');
- l10n.localizeElementFromTag(messageDiv, tag);
- remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
-}
-
-/**
* Updates current UI mode according to the current state of the setup
* flow and start the action corresponding to the current step (if
* any).
@@ -319,13 +309,14 @@ remoting.HostSetupDialog.prototype.updateState_ = function() {
} else if (state == remoting.HostSetupFlow.State.INSTALL_HOST) {
this.installHost_();
} else if (state == remoting.HostSetupFlow.State.STARTING_HOST) {
- this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STARTING');
+ remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STARTING');
this.startHost_();
} else if (state == remoting.HostSetupFlow.State.UPDATING_PIN) {
- this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_UPDATING_PIN');
+ remoting.showSetupProcessingMessage(
+ /*i18n-content*/'HOST_SETUP_UPDATING_PIN');
this.updatePin_();
} else if (state == remoting.HostSetupFlow.State.STOPPING_HOST) {
- this.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STOPPING');
+ remoting.showSetupProcessingMessage(/*i18n-content*/'HOST_SETUP_STOPPING');
this.stopHost_();
} else if (state == remoting.HostSetupFlow.State.HOST_STARTED) {
// TODO(jamiewalch): Only display the second string if the computer's power
@@ -362,15 +353,8 @@ remoting.HostSetupDialog.prototype.installHost_ = function() {
that.updateState_();
};
- /** @param {remoting.HostController.AsyncResult} asyncResult */
- var onDone = function(asyncResult) {
- if (asyncResult == remoting.HostController.AsyncResult.OK) {
- that.hostController_.getLocalHostState(onHostState);
- } else if (asyncResult == remoting.HostController.AsyncResult.CANCELLED) {
- onError(remoting.Error.CANCELLED);
- } else {
- onError(remoting.Error.UNEXPECTED);
- }
+ var onDone = function() {
+ that.hostController_.getLocalHostState(onHostState);
};
/** @param {remoting.HostController.State} state */
@@ -383,34 +367,14 @@ remoting.HostSetupDialog.prototype.installHost_ = function() {
that.flow_.switchToNextStep();
that.updateState_();
} else {
- // For Mac/Linux, prompt the user again if the host is not installed.
- if (navigator.platform != 'Win32') {
- hostInstallDialog.tryAgain();
- } else {
- // For Windows, report an error in the unlikely case that
- // HostController.installHost reports AsyncResult.OK but the host is not
- // installed.
- console.error('The chromoting host is not installed.');
- onError(remoting.Error.UNEXPECTED);
- }
+ // Prompt the user again if the host is not installed.
+ hostInstallDialog.tryAgain();
}
};
- if (navigator.platform == 'Win32') {
- // Currently we show two dialogs (each with a UAC prompt) when a user
- // enables the host for the first time, one for installing the host (by the
- // plugin) and the other for starting the host (by the native messaging
- // host). We'd like to reduce it to one but don't have a good solution
- // right now.
- // We also show the same message on the two dialogs because. We don't want
- // to confuse the user by saying "Installing Remote Desktop" because in
- // their mind "Remote Deskto" (the webapp) has already been installed.
- that.showProcessingMessage_(/*i18n-content*/'HOST_SETUP_STARTING');
- }
-
/** @type {remoting.HostInstallDialog} */
var hostInstallDialog = new remoting.HostInstallDialog();
- hostInstallDialog.show(this.hostController_, onDone, onError);
+ hostInstallDialog.show(onDone, onError);
}
/**
diff --git a/remoting/webapp/html/template_main.html b/remoting/webapp/html/template_main.html
index e690e3fb45..e49551dc4f 100644
--- a/remoting/webapp/html/template_main.html
+++ b/remoting/webapp/html/template_main.html
@@ -5,7 +5,7 @@ Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
-<html class="scrollable full-height">
+<html class="full-height">
<head>
<meta charset="utf-8">
<link rel="icon" type="image/png" href="chromoting16.webp">
@@ -14,13 +14,16 @@ found in the LICENSE file.
<link rel="stylesheet" href="main.css">
<link rel="stylesheet" href="menu_button.css">
<link rel="stylesheet" href="toolbar.css">
+ <link rel="stylesheet" href="window_frame.css">
<meta-include type="javascript"/>
<title i18n-content="PRODUCT_NAME"></title>
</head>
- <body class="full-height">
+ <body class="full-height inner-border-for-apps-v2">
+
+ <meta-include src="webapp/html/window_frame.html"/>
<!-- loading-mode is initially visible, but becomes hidden as soon as an
AppMode is selected by remoting.init. All other divs are initially
@@ -34,62 +37,65 @@ found in the LICENSE file.
<iframe id="wcs-sandbox" src="wcs_sandbox.html" hidden></iframe>
- <div class="inset" data-ui-mode="home" hidden>
+ <div id="scroller">
+ <div class="inset" data-ui-mode="home" hidden>
- <meta-include src="webapp/html/ui_header.html"/>
- <meta-include src="webapp/html/butterbar.html"/>
- <meta-include src="webapp/html/ui_it2me.html"/>
- <meta-include src="webapp/html/ui_me2me.html"/>
+ <meta-include src="webapp/html/ui_header.html"/>
+ <meta-include src="webapp/html/butterbar.html"/>
+ <meta-include src="webapp/html/ui_it2me.html"/>
+ <meta-include src="webapp/html/ui_me2me.html"/>
- </div>
+ </div> <!-- inset -->
- <meta-include src="webapp/html/dialog_auth.html"/>
+ <meta-include src="webapp/html/dialog_auth.html"/>
- <div class="dialog-screen"
- data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings"
- hidden></div>
+ <div class="dialog-screen"
+ data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-setup home.token-refresh-failed home.manage-pairings home.host-setup home.host-install"
+ hidden></div>
- <div class="dialog-container"
- data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings"
- hidden>
+ <div class="dialog-container"
+ data-ui-mode="home.host home.client home.history home.confirm-host-delete home.host-install home.host-setup home.token-refresh-failed home.manage-pairings"
+ hidden>
- <meta-include src="webapp/html/dialog_token_refresh_failed.html"/>
- <meta-include src="webapp/html/dialog_host_setup.html"/>
- <meta-include src="webapp/html/dialog_host_install.html"/>
- <meta-include src="webapp/html/dialog_host.html"/>
+ <meta-include src="webapp/html/dialog_token_refresh_failed.html"/>
+ <meta-include src="webapp/html/dialog_host_setup.html"/>
+ <meta-include src="webapp/html/dialog_host_install.html"/>
+ <meta-include src="webapp/html/dialog_host.html"/>
- <div id="client-dialog"
- class="kd-modaldialog"
- data-ui-mode="home.client">
+ <div id="client-dialog"
+ class="kd-modaldialog"
+ data-ui-mode="home.client">
- <meta-include src="webapp/html/dialog_client_unconnected.html"/>
- <meta-include src="webapp/html/dialog_client_connecting.html"/>
- <meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/>
- <meta-include src="webapp/html/dialog_client_pin_prompt.html"/>
- <meta-include src="webapp/html/dialog_client_third_party_auth.html"/>
- <meta-include src="webapp/html/dialog_client_connect_failed.html"/>
- <meta-include src="webapp/html/dialog_client_session_finished.html"/>
+ <meta-include src="webapp/html/dialog_client_unconnected.html"/>
+ <meta-include src="webapp/html/dialog_client_connecting.html"/>
+ <meta-include src="webapp/html/dialog_client_host_needs_upgrade.html"/>
+ <meta-include src="webapp/html/dialog_client_pin_prompt.html"/>
+ <meta-include src="webapp/html/dialog_client_third_party_auth.html"/>
+ <meta-include src="webapp/html/dialog_client_connect_failed.html"/>
+ <meta-include src="webapp/html/dialog_client_session_finished.html"/>
- </div>
+ </div>
- <meta-include src="webapp/html/dialog_connection_history.html"/>
- <meta-include src="webapp/html/dialog_confirm_host_delete.html"/>
- <meta-include src="webapp/html/dialog_manage_pairings.html"/>
+ <meta-include src="webapp/html/dialog_connection_history.html"/>
+ <meta-include src="webapp/html/dialog_confirm_host_delete.html"/>
+ <meta-include src="webapp/html/dialog_manage_pairings.html"/>
- </div> <!-- dialog-container -->
+ </div> <!-- dialog-container -->
- <div id="session-mode"
- data-ui-mode="in-session home.client"
- class="full-height"
- hidden>
+ <div id="session-mode"
+ data-ui-mode="in-session home.client"
+ class="full-height"
+ hidden>
- <meta-include src="webapp/html/toolbar.html"/>
- <meta-include src="webapp/html/client_plugin.html"/>
+ <meta-include src="webapp/html/toolbar.html"/>
+ <meta-include src="webapp/html/client_plugin.html"/>
- </div>
+ </div> <!-- session-mode -->
+
+ <div id="statistics" dir="ltr" class="selectable" hidden>
+ </div>
- <div id="statistics" dir="ltr" class="selectable" hidden>
- </div>
+ </div> <!-- scroller ->
</body>
</html>
diff --git a/remoting/webapp/html/toolbar.html b/remoting/webapp/html/toolbar.html
index eeead1564b..74fecbd619 100644
--- a/remoting/webapp/html/toolbar.html
+++ b/remoting/webapp/html/toolbar.html
@@ -23,7 +23,8 @@ found in the LICENSE file.
<button id="toolbar-disconnect"
type="button"
- i18n-content="DISCONNECT_MYSELF_BUTTON">
+ i18n-content="DISCONNECT_MYSELF_BUTTON"
+ class="apps-v1-only">
</button>
<span class="menu-button" id="send-keys-menu">
@@ -47,9 +48,12 @@ found in the LICENSE file.
<ul>
<li id="screen-resize-to-client"
i18n-content="RESIZE_TO_CLIENT"></li>
- <li id="screen-shrink-to-fit" i18n-content="SHRINK_TO_FIT"></li>
- <li class="menu-separator"></li>
- <li id="toggle-full-screen" i18n-content="FULL_SCREEN"></li>
+ <li id="screen-shrink-to-fit"
+ i18n-content="SHRINK_TO_FIT"></li>
+ <li class="menu-separator apps-v1-only"></li>
+ <li id="toggle-full-screen"
+ i18n-content="FULL_SCREEN"
+ class="apps-v1-only"></li>
</ul>
</span>
diff --git a/remoting/webapp/html/window_frame.html b/remoting/webapp/html/window_frame.html
new file mode 100644
index 0000000000..3b24d4890f
--- /dev/null
+++ b/remoting/webapp/html/window_frame.html
@@ -0,0 +1,29 @@
+<!--
+Copyright 2014 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<div id="title-bar" class="title-bar apps-v2-only">
+ <span class="window-title">&nbsp;</span>
+ <span class="window-controls-hover-target">
+ <div class="window-controls">
+ <span i18n-title="DISCONNECT_MYSELF_BUTTON"
+ class="window-control window-disconnect">
+ <img src="icon_disconnect.webp">
+ </span>
+ <span i18n-title="MINIMIZE_WINDOW"
+ class="window-control window-minimize">
+ <img src="icon_minimize.webp">
+ </span>
+ <span i18n-title="MAXIMIZE_WINDOW"
+ class="window-control window-maximize-restore">
+ <img src="icon_maximize_restore.webp">
+ </span>
+ <span i18n-title="CLOSE_WINDOW"
+ class="window-control window-close">
+ <img src="icon_close.webp">
+ </span>
+ </div>
+ <div class="window-controls-stub">&nbsp;</div>
+ </span>
+</div>
diff --git a/remoting/webapp/js_proto/chrome_proto.js b/remoting/webapp/js_proto/chrome_proto.js
index 273910e296..62ddc12bad 100644
--- a/remoting/webapp/js_proto/chrome_proto.js
+++ b/remoting/webapp/js_proto/chrome_proto.js
@@ -287,6 +287,7 @@ var AppWindow = function() {
AppWindow.prototype.close = function() {};
AppWindow.prototype.drawAttention = function() {};
+AppWindow.prototype.maximize = function() {};
AppWindow.prototype.minimize = function() {};
AppWindow.prototype.restore = function() {};
AppWindow.prototype.fullscreen = function() {};
diff --git a/remoting/webapp/js_proto/dom_proto.js b/remoting/webapp/js_proto/dom_proto.js
index 64f330ff79..1e392cb559 100644
--- a/remoting/webapp/js_proto/dom_proto.js
+++ b/remoting/webapp/js_proto/dom_proto.js
@@ -167,3 +167,46 @@ var MediaSource = function() {}
* @return {SourceBuffer}
*/
MediaSource.prototype.addSourceBuffer = function(format) {}
+
+/**
+ * @constructor
+ * @param {function(function(*), function(*)) : void} init
+ */
+var Promise = function (init) {};
+
+/**
+ * @param {function(*) : void} onFulfill
+ * @param {function(*) : void} onReject
+ * @return {Promise}
+ */
+Promise.prototype.then = function (onFulfill, onReject) {};
+
+/**
+ * @param {function(*) : void} onReject
+ * @return {Promise}
+ */
+Promise.prototype['catch'] = function (onReject) {};
+
+/**
+ * @param {Array.<Promise>} promises
+ * @return {Promise}
+ */
+Promise.prototype.race = function (promises) {}
+
+/**
+ * @param {Array.<Promise>} promises
+ * @return {Promise}
+ */
+Promise.prototype.all = function (promises) {};
+
+/**
+ * @param {*} reason
+ * @return {Promise}
+ */
+Promise.reject = function (reason) {};
+
+/**
+ * @param {*} value
+ * @return {Promise}
+ */
+Promise.resolve = function (value) {};
diff --git a/remoting/webapp/main.css b/remoting/webapp/main.css
index ec616a832b..ea741ed950 100644
--- a/remoting/webapp/main.css
+++ b/remoting/webapp/main.css
@@ -20,6 +20,7 @@ tfoot, thead, tr, th, td, button {
.inset {
padding: 20px 20px 0 20px;
+ position: relative;
}
body {
@@ -30,6 +31,7 @@ body {
direction: __MSG_@@bidi_dir__;
}
+
/*
* The "app-v2" class is added to the <html> node by remoting.init if it's
* running as a V2 app.
@@ -621,7 +623,7 @@ button {
}
.dialog-screen {
- position: fixed;
+ position: absolute;
top: 0;
left: 0;
width: 100%;
diff --git a/remoting/webapp/manifest.json.jinja2 b/remoting/webapp/manifest.json.jinja2
index 9ae27be0e3..ed3907ce47 100644
--- a/remoting/webapp/manifest.json.jinja2
+++ b/remoting/webapp/manifest.json.jinja2
@@ -42,6 +42,19 @@
"optional_permissions": [
"<all_urls>"
],
+
+{% if webapp_type != 'v1' %}
+ "oauth2": {
+ "client_id": "{{ REMOTING_IDENTITY_API_CLIENT_ID }}",
+ "scopes": [
+ "https://www.googleapis.com/auth/chromoting https://www.googleapis.com/auth/googletalk https://www.googleapis.com/auth/userinfo#email"
+ ]
+ },
+ "sandbox": {
+ "pages": [ "wcs_sandbox.html" ]
+ },
+{% endif %}
+
"permissions": [
"{{ OAUTH2_ACCOUNTS_HOST }}/*",
"{{ OAUTH2_API_BASE_URL }}/*",
@@ -59,30 +72,18 @@
"contextMenus",
"overrideEscFullscreen"
{% endif %}
- ],
-
-{% if webapp_type == 'v1' %}
- "plugins": [
- { "path": "remoting_host_plugin.dll", "public": false },
- { "path": "libremoting_host_plugin.ia32.so", "public": false },
- { "path": "libremoting_host_plugin.x64.so", "public": false },
- { "path": "remoting_host_plugin.plugin", "public": false }
- ],
- "requirements": {
- "plugins": {
- "npapi": false
+{% if webapp_type == 'v2_pnacl' %}
+ ,{
+ "socket": [
+ "tcp-connect",
+ "tcp-listen",
+ "udp-send-to",
+ "udp-bind",
+ "udp-multicast-membership",
+ "resolve-host",
+ "network-state"
+ ]
}
- }
-{% else %}
- "oauth2": {
- "client_id": "{{ REMOTING_IDENTITY_API_CLIENT_ID }}",
- "scopes": [
- "https://www.googleapis.com/auth/chromoting https://www.googleapis.com/auth/googletalk https://www.googleapis.com/auth/userinfo#email"
- ]
- },
- "sandbox": {
- "pages": [ "wcs_sandbox.html" ]
- }
{% endif %}
-
+ ]
}
diff --git a/remoting/webapp/plugin_settings.js b/remoting/webapp/plugin_settings.js
index 8d0a1669f1..2d1f26496a 100644
--- a/remoting/webapp/plugin_settings.js
+++ b/remoting/webapp/plugin_settings.js
@@ -54,3 +54,6 @@ remoting.Settings.prototype.THIRD_PARTY_AUTH_REDIRECT_URI =
// Whether to use MediaSource API for video rendering.
remoting.Settings.prototype.USE_MEDIA_SOURCE_RENDERING = false;
+
+// 'native', 'nacl' or 'pnacl'.
+remoting.Settings.prototype.CLIENT_PLUGIN_TYPE = 'CLIENT_PLUGIN_TYPE';
diff --git a/remoting/webapp/remoting.js b/remoting/webapp/remoting.js
index 893edfd448..52357f7422 100644
--- a/remoting/webapp/remoting.js
+++ b/remoting/webapp/remoting.js
@@ -69,6 +69,8 @@ remoting.init = function() {
if (remoting.isAppsV2) {
remoting.identity = new remoting.Identity(consentRequired_);
remoting.fullscreen = new remoting.FullscreenAppsV2();
+ remoting.windowFrame = new remoting.WindowFrame(
+ document.getElementById('title-bar'));
} else {
remoting.oauth2 = new remoting.OAuth2();
if (!remoting.oauth2.isAuthenticated()) {
@@ -188,15 +190,14 @@ remoting.createNpapiPlugin = function(container) {
// Hiding the plugin means it doesn't load, so make it size zero instead.
plugin.width = 0;
plugin.height = 0;
- container.appendChild(plugin);
// Verify if the plugin was loaded successfully.
- if (!plugin.hasOwnProperty('REQUESTED_ACCESS_CODE')) {
- container.removeChild(plugin);
- return null;
+ if (plugin.hasOwnProperty('REQUESTED_ACCESS_CODE')) {
+ container.appendChild(plugin);
+ return /** @type {remoting.HostPlugin} */ (plugin);
}
- return /** @type {remoting.HostPlugin} */ (plugin);
+ return null;
};
/**
diff --git a/remoting/webapp/remoting_client_pnacl.nmf b/remoting/webapp/remoting_client_pnacl.nmf
new file mode 100644
index 0000000000..8181c0a0ea
--- /dev/null
+++ b/remoting/webapp/remoting_client_pnacl.nmf
@@ -0,0 +1,10 @@
+{
+ "program": {
+ "portable": {
+ "pnacl-translate": {
+ "url": "remoting_client_plugin_newlib.pexe",
+ "optlevel": 2
+ }
+ }
+ }
+}
diff --git a/remoting/webapp/smart_reconnector.js b/remoting/webapp/smart_reconnector.js
index 55186b4205..7cc50bfb02 100644
--- a/remoting/webapp/smart_reconnector.js
+++ b/remoting/webapp/smart_reconnector.js
@@ -62,8 +62,7 @@ remoting.SmartReconnector = function(connector, clientSession) {
// to connect.
remoting.SmartReconnector.kReconnectDelay = 2000;
-// If no frames are received from the server for more than |kConnectionTimeout|,
-// disconnect the session.
+// If the video channel is inactive for 10 seconds reconnect the session.
remoting.SmartReconnector.kConnectionTimeout = 10000;
remoting.SmartReconnector.prototype = {
@@ -97,22 +96,13 @@ remoting.SmartReconnector.prototype = {
},
/**
- * @param {boolean} active This function is called if no frames are received
- * on the client for more than 1 second.
+ * @param {boolean} active True if the video channel is active.
*/
videoChannelStateChanged_: function (active) {
this.cancelPending_();
if (!active) {
- // If the channel becomes inactive due to a lack of network connection,
- // wait for it to go online. The plugin will try to reconnect the video
- // channel once it is online. If the video channels doesn't finish
- // reconnecting within the timeout, tear down the session and reconnect.
- if (navigator.onLine) {
- this.reconnect_();
- } else {
- window.addEventListener(
+ window.addEventListener(
'online', this.bound_.startReconnectTimeout, false);
- }
}
},
diff --git a/remoting/webapp/toolbar.css b/remoting/webapp/toolbar.css
index 3726a290a0..a039227759 100644
--- a/remoting/webapp/toolbar.css
+++ b/remoting/webapp/toolbar.css
@@ -4,7 +4,7 @@
*/
.toolbar-container {
- position: fixed;
+ position: absolute;
top: -48px;
width: 640px;
-webkit-transition: top 0.15s ease;
diff --git a/remoting/webapp/ui_mode.js b/remoting/webapp/ui_mode.js
index e9d8474a2d..16e5e5354a 100644
--- a/remoting/webapp/ui_mode.js
+++ b/remoting/webapp/ui_mode.js
@@ -276,3 +276,12 @@ function confineOrRestoreFocus_(mutations) {
}
}
}
+
+/**
+ * @param {string} tag
+ */
+remoting.showSetupProcessingMessage = function(tag) {
+ var messageDiv = document.getElementById('host-setup-processing-message');
+ l10n.localizeElementFromTag(messageDiv, tag);
+ remoting.setMode(remoting.AppMode.HOST_SETUP_PROCESSING);
+}
diff --git a/remoting/webapp/window_frame.css b/remoting/webapp/window_frame.css
new file mode 100644
index 0000000000..a079ec1c81
--- /dev/null
+++ b/remoting/webapp/window_frame.css
@@ -0,0 +1,174 @@
+/* Copyright 2014 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+html.apps-v2,
+html.apps-v2 body {
+ height: 100%;
+ width: 100%;
+}
+
+html.apps-v2 body:not(.fullscreen) {
+ border: 1px solid gray; /* This is the window border. */
+}
+
+html.apps-v2 .title-bar {
+ border-bottom: 1px solid gray;
+ z-index: 100;
+}
+
+.window-title,
+.window-controls-hover-target {
+ height: 32px;
+ line-height: 32px;
+ font-size: 16px;
+ background-color: #c4c4c4;
+}
+
+.title-bar .window-title {
+ padding-__MSG_@@bidi_start_edge__: 12px;
+ width: 100%;
+ display: inline-block;
+ -webkit-app-region: drag;
+}
+
+.window-controls-hover-target {
+ -webkit-app-region: no-drag;
+ position: fixed;
+ top: 1px;
+ __MSG_@@bidi_end_edge__: 1px;
+}
+
+.window-controls-hover-target {
+ display: table;
+}
+
+.window-controls-hover-target > div:first-child {
+ display: table-row;
+}
+
+.window-control {
+ height: 32px;
+ width: 32px;
+ text-align: center;
+ display: inline-block;
+ border-__MSG_@@bidi_start_edge__: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+.window-control:hover {
+ background-color: #d5d5d5;
+}
+
+.window-control:active {
+ background-color: #a6a6a6;
+}
+
+.window-control > img {
+ margin-bottom: -2px;
+}
+
+.window-controls-stub {
+ display: none;
+ -webkit-column-span: all;
+ line-height: 3px;
+ background: url("drag.webp");
+ border-top: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+#scroller {
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+ position: relative;
+}
+
+html.apps-v2 #scroller {
+ height: calc(100% - 32px); /** Allow space for the title-bar */
+}
+
+/* Add an etched border to the window controls, title bar and stub */
+.title-bar,
+.window-control,
+.window-controls-stub {
+ position: relative;
+}
+
+.title-bar:after,
+.window-control:after,
+.window-controls-stub:after {
+ content: "";
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border-left: 1px solid rgba(255, 255, 255, 0.2);
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
+ pointer-events: none;
+}
+
+
+/* When connected to a host, the Disconnect button is displayed. */
+body:not(.connected) .window-disconnect {
+ display: none;
+}
+
+
+/*
+ * When in full-screen mode, significant changes are made:
+ * - The scroll-bars are removed.
+ * - The window controls have a border (so the left-border of the first button
+ * is not needed).
+ * - The title-bar (and its bottom border) are not displayed.
+ * - The stub is visible.
+ * - The window controls gain transition effects for position and opacity and
+ * auto-hide behind the top edge of the screen.
+ * - A border is added to the window controls to ensure they stand out against
+ * any desktop.
+ * - The window border is removed.
+ */
+
+html.apps-v2 body.fullscreen #scroller {
+ height: 100%;
+ overflow: hidden;
+}
+
+body.fullscreen .window-controls-hover-target {
+ border: 1px solid #a6a6a6;
+}
+
+body.fullscreen .window-control:first-child {
+ border-__MSG_@@bidi_start_edge__: none;
+}
+
+body.fullscreen .window-title {
+ display: none;
+}
+
+body.fullscreen .title-bar {
+ border-bottom: none;
+}
+
+body.fullscreen .window-controls-stub {
+ display: table-cell;
+}
+
+body.fullscreen .window-controls-hover-target {
+ transition-property: opacity, box-shadow, top;
+ transition-duration: 0.3s;
+ opacity: 0.7;
+ top: -33px;
+ __MSG_@@bidi_end_edge__: 8px;
+}
+
+body.fullscreen .window-controls-hover-target:hover,
+body.fullscreen .window-controls-hover-target.opened {
+ top: -4px;
+ opacity: 1.0;
+ box-shadow: 1px 1px 10px rgba(0, 0, 0, 0.5);
+}
+
+.fullscreen .window-controls-hover-target.opened .window-controls-stub {
+ background-color: #a6a6a6;
+}
diff --git a/remoting/webapp/window_frame.js b/remoting/webapp/window_frame.js
new file mode 100644
index 0000000000..b4f3d740af
--- /dev/null
+++ b/remoting/webapp/window_frame.js
@@ -0,0 +1,175 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * Apps v2 custom title bar implementation
+ */
+
+'use strict';
+
+/** @suppress {duplicate} */
+var remoting = remoting || {};
+
+/**
+ * @param {HTMLElement} titleBar The root node of the title-bar DOM hierarchy.
+ * @constructor
+ */
+remoting.WindowFrame = function(titleBar) {
+ /**
+ * @type {boolean}
+ * @private
+ */
+ this.isConnected_ = false;
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.titleBar_ = titleBar;
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.hoverTarget_ = /** @type {HTMLElement} */
+ (titleBar.querySelector('.window-controls-hover-target'));
+ base.debug.assert(this.hoverTarget_ != null);
+
+ /**
+ * @type {HTMLElement}
+ * @private
+ */
+ this.maximizeRestoreControl_ = /** @type {HTMLElement} */
+ (titleBar.querySelector('.window-maximize-restore'));
+ base.debug.assert(this.maximizeRestoreControl_ != null);
+
+ /**
+ * @type {Array.<{cls:string, fn: function()}>}
+ */
+ var handlers = [
+ { cls: 'window-disconnect', fn: this.disconnectSession_.bind(this) },
+ { cls: 'window-maximize-restore',
+ fn: this.maximizeOrRestoreWindow_.bind(this) },
+ { cls: 'window-minimize', fn: this.minimizeWindow_.bind(this) },
+ { cls: 'window-close', fn: window.close.bind(window) },
+ { cls: 'window-controls-stub', fn: this.toggleWindowControls_.bind(this) }
+ ];
+ for (var i = 0; i < handlers.length; ++i) {
+ var element = titleBar.querySelector('.' + handlers[i].cls);
+ base.debug.assert(element != null);
+ element.addEventListener('click', handlers[i].fn, false);
+ }
+
+ // Ensure that tool-tips are always correct.
+ this.updateMaximizeOrRestoreIconTitle_();
+ chrome.app.window.current().onMaximized.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+ chrome.app.window.current().onRestored.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+ chrome.app.window.current().onFullscreened.addListener(
+ this.updateMaximizeOrRestoreIconTitle_.bind(this));
+};
+
+/**
+ * @param {boolean} isConnected True if there is a connection active.
+ */
+remoting.WindowFrame.prototype.setConnected = function(isConnected) {
+ this.isConnected_ = isConnected;
+ if (this.isConnected_) {
+ document.body.classList.add('connected');
+ } else {
+ document.body.classList.remove('connected');
+ }
+ this.updateMaximizeOrRestoreIconTitle_();
+};
+
+/**
+ * @return {{width: number, height: number}} The size of the window, ignoring
+ * the title-bar and window borders, if visible.
+ */
+remoting.WindowFrame.prototype.getClientArea = function() {
+ if (chrome.app.window.current().isFullscreen()) {
+ return { 'height': window.innerHeight, 'width': window.innerWidth };
+ } else {
+ var kBorderWidth = 1;
+ var titleHeight = this.titleBar_.clientHeight;
+ return {
+ 'height': window.innerHeight - titleHeight - 2 * kBorderWidth,
+ 'width': window.innerWidth - 2 * kBorderWidth
+ };
+ }
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.disconnectSession_ = function() {
+ // When the user disconnects, exit full-screen mode. This should not be
+ // necessary, as we do the same thing in client_session.js when the plugin
+ // is removed. However, there seems to be a bug in chrome.AppWindow.restore
+ // that causes it to get stuck in full-screen mode without this.
+ if (chrome.app.window.current().isFullscreen()) {
+ chrome.app.window.current().restore();
+ chrome.app.window.current().restore();
+ }
+ remoting.disconnect();
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.maximizeOrRestoreWindow_ = function() {
+ /** @type {boolean} */
+ var restore =
+ chrome.app.window.current().isFullscreen() ||
+ chrome.app.window.current().isMaximized();
+ if (restore) {
+ // Restore twice: once to exit full-screen and once to exit maximized.
+ // If the app is not full-screen, or went full-screen without first
+ // being maximized, then the second restore has no effect.
+ chrome.app.window.current().restore();
+ chrome.app.window.current().restore();
+ } else {
+ chrome.app.window.current().maximize();
+ }
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.minimizeWindow_ = function() {
+ chrome.app.window.current().minimize();
+};
+
+/**
+ * @private
+ */
+remoting.WindowFrame.prototype.toggleWindowControls_ = function() {
+ this.hoverTarget_.classList.toggle('opened');
+};
+
+/**
+ * Update the tool-top for the maximize/full-screen/restore icon to reflect
+ * its current behaviour.
+ *
+ * @private
+ */
+remoting.WindowFrame.prototype.updateMaximizeOrRestoreIconTitle_ = function() {
+ /** @type {string} */
+ var tag = '';
+ if (chrome.app.window.current().isFullscreen()) {
+ tag = /*i18n-content*/'EXIT_FULL_SCREEN';
+ } else if (chrome.app.window.current().isMaximized()) {
+ tag = /*i18n-content*/'RESTORE_WINDOW';
+ } else if (this.isConnected_) {
+ tag = /*i18n-content*/'FULL_SCREEN';
+ } else {
+ tag = /*i18n-content*/'MAXIMIZE_WINDOW';
+ }
+ this.maximizeRestoreControl_.title = l10n.getTranslationOrError(tag);
+};
+
+/** @type {remoting.WindowFrame} */
+remoting.windowFrame = null; \ No newline at end of file