// Copyright (c) 2011 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. // // This is an application of a minimal host process in a Chromoting // system. It serves the purpose of gluing different pieces together // to make a functional host process for testing. // // It peforms the following functionality: // 1. Connect to the GTalk network and register the machine as a host. // 2. Accepts connection through libjingle. // 3. Receive mouse / keyboard events through libjingle. // 4. Sends screen capture through libjingle. #include #include #include #include "build/build_config.h" #include "base/at_exit.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/environment.h" #include "base/file_path.h" #include "base/logging.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/test/mock_chrome_application_mac.h" #include "base/threading/thread.h" #include "crypto/nss_util.h" #include "remoting/base/constants.h" #include "remoting/base/tracer.h" #include "remoting/host/capturer_fake.h" #include "remoting/host/chromoting_host.h" #include "remoting/host/chromoting_host_context.h" #include "remoting/host/continue_window.h" #include "remoting/host/curtain.h" #include "remoting/host/desktop_environment.h" #include "remoting/host/disconnect_window.h" #include "remoting/host/event_executor.h" #include "remoting/host/heartbeat_sender.h" #include "remoting/host/local_input_monitor.h" #include "remoting/host/json_host_config.h" #include "remoting/host/register_support_host_request.h" #include "remoting/host/self_access_verifier.h" #include "remoting/host/support_access_verifier.h" #include "remoting/proto/video.pb.h" #if defined(TOOLKIT_USES_GTK) #include "ui/gfx/gtk_util.h" #endif #if defined(OS_WIN) // TODO(garykac) Make simple host into a proper GUI app on Windows so that we // have an hModule for the dialog resource. HMODULE g_hModule = NULL; #endif using remoting::ChromotingHost; using remoting::DesktopEnvironment; using remoting::kChromotingTokenDefaultServiceName; using remoting::kXmppAuthServiceConfigPath; using remoting::protocol::CandidateSessionConfig; using remoting::protocol::ChannelConfig; using std::string; using std::wstring; #if defined(OS_WIN) const wchar_t kDefaultConfigPath[] = L".ChromotingConfig.json"; const wchar_t kHomeDrive[] = L"HOMEDRIVE"; const wchar_t kHomePath[] = L"HOMEPATH"; // TODO(sergeyu): Use environment utils from base/environment.h. const wchar_t* GetEnvironmentVar(const wchar_t* x) { return _wgetenv(x); } #else const char kDefaultConfigPath[] = ".ChromotingConfig.json"; static char* GetEnvironmentVar(const char* x) { return getenv(x); } #endif const char kFakeSwitchName[] = "fake"; const char kIT2MeSwitchName[] = "it2me"; const char kConfigSwitchName[] = "config"; const char kVideoSwitchName[] = "video"; const char kVideoSwitchValueVerbatim[] = "verbatim"; const char kVideoSwitchValueZip[] = "zip"; const char kVideoSwitchValueVp8[] = "vp8"; const char kVideoSwitchValueVp8Rtp[] = "vp8rtp"; class SimpleHost { public: SimpleHost() : fake_(false), is_it2me_(false) { } int Run() { // |message_loop| is declared early so that any code we call into which // requires a current message-loop won't complain. // It needs to be a UI message loop to keep runloops spinning on the Mac. MessageLoop message_loop(MessageLoop::TYPE_UI); remoting::ChromotingHostContext context( base::MessageLoopProxy::current()); context.Start(); base::Thread file_io_thread("FileIO"); file_io_thread.Start(); FilePath config_path = GetConfigPath(); scoped_refptr config = new remoting::JsonHostConfig( config_path, file_io_thread.message_loop_proxy()); if (!config->Read()) { LOG(ERROR) << "Failed to read configuration file " << config_path.value(); context.Stop(); return 1; } // For the simple host, we assume we always use the ClientLogin token for // chromiumsync because we do not have an HTTP stack with which we can // easily request an OAuth2 access token even if we had a RefreshToken for // the account. config->SetString(kXmppAuthServiceConfigPath, kChromotingTokenDefaultServiceName); // Initialize AccessVerifier. // TODO(jamiewalch): For the IT2Me case, the access verifier is passed to // RegisterSupportHostRequest::Init, so transferring ownership of it to the // ChromotingHost could cause a crash condition if SetIT2MeAccessCode is // called after the ChromotingHost is destroyed (for example, at shutdown). // Fix this. scoped_ptr access_verifier; scoped_ptr register_request; scoped_ptr heartbeat_sender; if (is_it2me_) { scoped_ptr support_access_verifier( new remoting::SupportAccessVerifier()); register_request.reset(new remoting::RegisterSupportHostRequest()); if (!register_request->Init( config, base::Bind(&SimpleHost::SetIT2MeAccessCode, base::Unretained(this), support_access_verifier.get()))) { return 1; } access_verifier.reset(support_access_verifier.release()); } else { scoped_ptr self_access_verifier( new remoting::SelfAccessVerifier()); if (!self_access_verifier->Init(config)) return 1; access_verifier.reset(self_access_verifier.release()); } // Construct a chromoting host. scoped_ptr desktop_environment; if (fake_) { remoting::Capturer* capturer = new remoting::CapturerFake(); remoting::EventExecutor* event_executor = remoting::EventExecutor::Create(context.desktop_message_loop(), capturer); remoting::Curtain* curtain = remoting::Curtain::Create(); remoting::DisconnectWindow* disconnect_window = remoting::DisconnectWindow::Create(); remoting::ContinueWindow* continue_window = remoting::ContinueWindow::Create(); remoting::LocalInputMonitor* local_input_monitor = remoting::LocalInputMonitor::Create(); desktop_environment.reset( new DesktopEnvironment(&context, capturer, event_executor, curtain, disconnect_window, continue_window, local_input_monitor)); } else { desktop_environment.reset(DesktopEnvironment::Create(&context)); } host_ = ChromotingHost::Create(&context, config, desktop_environment.get(), access_verifier.release(), false); host_->set_it2me(is_it2me_); if (protocol_config_.get()) { host_->set_protocol_config(protocol_config_.release()); } if (is_it2me_) { host_->AddStatusObserver(register_request.get()); } else { // Initialize HeartbeatSender. heartbeat_sender.reset( new remoting::HeartbeatSender(context.network_message_loop(), config)); if (!heartbeat_sender->Init()) return 1; host_->AddStatusObserver(heartbeat_sender.get()); } // Let the chromoting host run until the shutdown task is executed. host_->Start(); message_loop.MessageLoop::Run(); // And then stop the chromoting context. context.Stop(); file_io_thread.Stop(); host_ = NULL; return 0; } void set_config_path(const FilePath& config_path) { config_path_ = config_path; } void set_fake(bool fake) { fake_ = fake; } void set_is_it2me(bool is_it2me) { is_it2me_ = is_it2me; } void set_protocol_config(CandidateSessionConfig* protocol_config) { protocol_config_.reset(protocol_config); } private: // TODO(wez): This only needs to be a member because it needs access to the // ChromotingHost, which has to be created after the SupportAccessVerifier. void SetIT2MeAccessCode(remoting::SupportAccessVerifier* access_verifier, bool successful, const std::string& support_id, const base::TimeDelta& lifetime) { access_verifier->OnIT2MeHostRegistered(successful, support_id); if (successful) { std::string access_code = support_id + access_verifier->host_secret(); std::cout << "Support id: " << access_code << std::endl; // Tell the ChromotingHost the access code, to use as shared-secret. host_->set_access_code(access_code); } else { LOG(ERROR) << "If you haven't done so recently, try running" << " remoting/tools/register_host.py."; } } FilePath GetConfigPath() { if (!config_path_.empty()) return config_path_; #if defined(OS_WIN) wstring home_path = GetEnvironmentVar(kHomeDrive); home_path += GetEnvironmentVar(kHomePath); #else string home_path = GetEnvironmentVar(base::env_vars::kHome); #endif return FilePath(home_path).Append(kDefaultConfigPath); } FilePath config_path_; bool fake_; bool is_it2me_; scoped_ptr protocol_config_; scoped_refptr host_; }; int main(int argc, char** argv) { // Needed for the Mac, so we don't leak objects when threads are created. base::mac::ScopedNSAutoreleasePool pool; CommandLine::Init(argc, argv); const CommandLine* cmd_line = CommandLine::ForCurrentProcess(); base::AtExitManager exit_manager; crypto::EnsureNSPRInit(); #if defined(TOOLKIT_USES_GTK) gfx::GtkInitFromCommandLine(*cmd_line); #endif // TOOLKIT_USES_GTK #if defined(OS_MACOSX) mock_cr_app::RegisterMockCrApp(); #endif // OS_MACOSX SimpleHost simple_host; if (cmd_line->HasSwitch(kConfigSwitchName)) { simple_host.set_config_path( cmd_line->GetSwitchValuePath(kConfigSwitchName)); } simple_host.set_fake(cmd_line->HasSwitch(kFakeSwitchName)); simple_host.set_is_it2me(cmd_line->HasSwitch(kIT2MeSwitchName)); if (cmd_line->HasSwitch(kVideoSwitchName)) { string video_codec = cmd_line->GetSwitchValueASCII(kVideoSwitchName); scoped_ptr config( CandidateSessionConfig::CreateDefault()); config->mutable_video_configs()->clear(); ChannelConfig::TransportType transport = ChannelConfig::TRANSPORT_STREAM; ChannelConfig::Codec codec; if (video_codec == kVideoSwitchValueVerbatim) { codec = ChannelConfig::CODEC_VERBATIM; } else if (video_codec == kVideoSwitchValueZip) { codec = ChannelConfig::CODEC_ZIP; } else if (video_codec == kVideoSwitchValueVp8) { codec = ChannelConfig::CODEC_VP8; } else if (video_codec == kVideoSwitchValueVp8Rtp) { transport = ChannelConfig::TRANSPORT_SRTP; codec = ChannelConfig::CODEC_VP8; } else { LOG(ERROR) << "Unknown video codec: " << video_codec; return 1; } config->mutable_video_configs()->push_back(ChannelConfig( transport, remoting::protocol::kDefaultStreamVersion, codec)); simple_host.set_protocol_config(config.release()); } return simple_host.Run(); }