diff options
author | jamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-13 22:42:38 +0000 |
---|---|---|
committer | jamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-06-13 22:42:38 +0000 |
commit | c78669c954f21f73734a1a484943d19525a58a5b (patch) | |
tree | 5a1ddd659dd13d1296692694d78e8b03fd9939ab | |
parent | 244d7c5866e4c7406251a499272abd76204fa005 (diff) | |
download | chromium_src-c78669c954f21f73734a1a484943d19525a58a5b.zip chromium_src-c78669c954f21f73734a1a484943d19525a58a5b.tar.gz chromium_src-c78669c954f21f73734a1a484943d19525a58a5b.tar.bz2 |
Block remote mouse inputs for a short time when local input is received.
BUG=
TEST=
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=88878
Review URL: http://codereview.chromium.org/7134048
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@88915 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | remoting/host/chromoting_host.cc | 38 | ||||
-rw-r--r-- | remoting/host/chromoting_host.h | 8 | ||||
-rw-r--r-- | remoting/host/chromoting_host_unittest.cc | 20 | ||||
-rw-r--r-- | remoting/host/client_session.cc | 58 | ||||
-rw-r--r-- | remoting/host/client_session.h | 15 | ||||
-rw-r--r-- | remoting/host/client_session_unittest.cc | 40 | ||||
-rw-r--r-- | remoting/host/desktop_environment.cc | 7 | ||||
-rw-r--r-- | remoting/host/desktop_environment.h | 13 | ||||
-rw-r--r-- | remoting/host/host_mock_objects.cc | 16 | ||||
-rw-r--r-- | remoting/host/host_mock_objects.h | 12 | ||||
-rw-r--r-- | remoting/host/local_input_monitor.h | 24 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_linux.cc | 52 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_mac.cc | 34 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_thread_linux.cc | 141 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_thread_linux.h | 31 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_win.cc | 34 | ||||
-rw-r--r-- | remoting/host/simple_host_process.cc | 5 | ||||
-rw-r--r-- | remoting/remoting.gyp | 4 |
18 files changed, 532 insertions, 20 deletions
diff --git a/remoting/host/chromoting_host.cc b/remoting/host/chromoting_host.cc index 1d0b484..36a1b45 100644 --- a/remoting/host/chromoting_host.cc +++ b/remoting/host/chromoting_host.cc @@ -17,6 +17,7 @@ #include "remoting/host/event_executor.h" #include "remoting/host/host_config.h" #include "remoting/host/host_key_pair.h" +#include "remoting/host/local_input_monitor.h" #include "remoting/host/screen_recorder.h" #include "remoting/host/user_authenticator.h" #include "remoting/proto/auth.pb.h" @@ -41,9 +42,10 @@ ChromotingHost* ChromotingHost::Create(ChromotingHostContext* context, EventExecutor::Create(context->ui_message_loop(), capturer); Curtain* curtain = Curtain::Create(); DisconnectWindow* disconnect_window = DisconnectWindow::Create(); + LocalInputMonitor* local_input_monitor = LocalInputMonitor::Create(); return Create(context, config, new DesktopEnvironment(capturer, event_executor, curtain, - disconnect_window), + disconnect_window, local_input_monitor), access_verifier); } @@ -66,6 +68,7 @@ ChromotingHost::ChromotingHost(ChromotingHostContext* context, state_(kInitial), protocol_config_(protocol::CandidateSessionConfig::CreateDefault()), is_curtained_(false), + is_monitoring_local_inputs_(false), is_me2mom_(false) { DCHECK(desktop_environment_.get()); } @@ -342,6 +345,21 @@ void ChromotingHost::set_protocol_config( protocol_config_.reset(config); } +void ChromotingHost::LocalMouseMoved(const gfx::Point& new_pos) { + if (MessageLoop::current() != context_->main_message_loop()) { + context_->main_message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &ChromotingHost::LocalMouseMoved, + new_pos)); + return; + } + ClientList::iterator client; + for (client = clients_.begin(); client != clients_.end(); ++client) { + client->get()->LocalMouseMoved(new_pos); + } +} + void ChromotingHost::OnServerClosed() { // Don't need to do anything here. } @@ -378,8 +396,10 @@ void ChromotingHost::OnClientDisconnected(ConnectionToClient* connection) { if (!HasAuthenticatedClients()) { EnableCurtainMode(false); - if (is_me2mom_) + if (is_me2mom_) { + MonitorLocalInputs(false); ShowDisconnectWindow(false, std::string()); + } } } @@ -427,6 +447,17 @@ void ChromotingHost::EnableCurtainMode(bool enable) { is_curtained_ = enable; } +void ChromotingHost::MonitorLocalInputs(bool enable) { + if (enable == is_monitoring_local_inputs_) + return; + if (enable) { + desktop_environment_->local_input_monitor()->Start(this); + } else { + desktop_environment_->local_input_monitor()->Stop(); + } + is_monitoring_local_inputs_ = enable; +} + void ChromotingHost::LocalLoginSucceeded( scoped_refptr<ConnectionToClient> connection) { if (MessageLoop::current() != context_->main_message_loop()) { @@ -473,8 +504,11 @@ void ChromotingHost::LocalLoginSucceeded( // Immediately add the connection and start the session. recorder_->AddConnection(connection); recorder_->Start(); + // TODO(jamiewalch): Tidy up actions to be taken on connect/disconnect, + // including closing the connection on failure of a critical operation. EnableCurtainMode(true); if (is_me2mom_) { + MonitorLocalInputs(true); std::string username = connection->session()->jid(); size_t pos = username.find('/'); if (pos != std::string::npos) diff --git a/remoting/host/chromoting_host.h b/remoting/host/chromoting_host.h index a890398..c05b06d 100644 --- a/remoting/host/chromoting_host.h +++ b/remoting/host/chromoting_host.h @@ -128,6 +128,10 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>, is_me2mom_ = is_me2mom; } + // Notify all active client sessions that local input has been detected, and + // that remote input should be ignored for a short time. + void LocalMouseMoved(const gfx::Point& new_pos); + private: friend class base::RefCountedThreadSafe<ChromotingHost>; friend class ChromotingHostTest; @@ -164,6 +168,8 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>, void EnableCurtainMode(bool enable); + void MonitorLocalInputs(bool enable); + void ProcessPreAuthentication( const scoped_refptr<protocol::ConnectionToClient>& connection); @@ -211,8 +217,8 @@ class ChromotingHost : public base::RefCountedThreadSafe<ChromotingHost>, // Configuration of the protocol. scoped_ptr<protocol::CandidateSessionConfig> protocol_config_; - // Whether or not the host is currently curtained. bool is_curtained_; + bool is_monitoring_local_inputs_; // Whether or not the host is running in "Me2Mom" mode, in which connections // are pre-authenticated, and hence the local login challenge can be bypassed. diff --git a/remoting/host/chromoting_host_unittest.cc b/remoting/host/chromoting_host_unittest.cc index 3e4c051..a24c8aa 100644 --- a/remoting/host/chromoting_host_unittest.cc +++ b/remoting/host/chromoting_host_unittest.cc @@ -90,9 +90,10 @@ class ChromotingHostTest : public testing::Test { event_executor_ = new MockEventExecutor(); curtain_ = new MockCurtain(); disconnect_window_ = new MockDisconnectWindow(); + local_input_monitor_ = new MockLocalInputMonitor(); DesktopEnvironment* desktop = new DesktopEnvironment(capturer, event_executor_, curtain_, - disconnect_window_); + disconnect_window_, local_input_monitor_); MockAccessVerifier* access_verifier = new MockAccessVerifier(); host_ = ChromotingHost::Create(&context_, config_, @@ -216,6 +217,7 @@ class ChromotingHostTest : public testing::Test { MockEventExecutor* event_executor_; MockCurtain* curtain_; MockDisconnectWindow* disconnect_window_; + MockLocalInputMonitor* local_input_monitor_; }; TEST_F(ChromotingHostTest, StartAndShutdown) { @@ -430,7 +432,7 @@ TEST_F(ChromotingHostTest, CurtainModeFailSecond) { ACTION_P(SetBool, var) { *var = true; } -TEST_F(ChromotingHostTest, CurtainModeMe2Mom) { +TEST_F(ChromotingHostTest, CurtainModeIT2Me) { host_->Start(NewRunnableFunction(&PostQuitTask, &message_loop_)); host_->set_me2mom(true); @@ -442,7 +444,7 @@ TEST_F(ChromotingHostTest, CurtainModeMe2Mom) { bool curtain_activated = false; std::string mockJid("user@domain/rest-of-jid"); { - InSequence s; + Sequence s1, s2; // Can't just expect Times(0) because if it fails then the host will // not be shut down and the message loop will never exit. EXPECT_CALL(*curtain_, EnableCurtainMode(_)) @@ -450,17 +452,25 @@ TEST_F(ChromotingHostTest, CurtainModeMe2Mom) { .WillRepeatedly(SetBool(&curtain_activated)); EXPECT_CALL(*session_, jid()) .Times(1) + .InSequence(s1) .WillOnce(ReturnRef(mockJid)); EXPECT_CALL(*disconnect_window_, Show(_, "user@domain")) - .Times(1); + .Times(1) + .InSequence(s1); + EXPECT_CALL(*local_input_monitor_, Start(_)) + .Times(1) + .InSequence(s2); EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) + .InSequence(s1, s2) .WillOnce(DoAll( InvokeWithoutArgs(host_.get(), &ChromotingHost::Shutdown), RunDoneTask())) .RetiresOnSaturation(); EXPECT_CALL(video_stub_, ProcessVideoPacket(_, _)) - .Times(AnyNumber()); + .Times(AnyNumber()) + .InSequence(s1, s2); EXPECT_CALL(*connection_.get(), Disconnect()) + .InSequence(s1, s2) .RetiresOnSaturation(); } SimulateClientConnection(0, true); diff --git a/remoting/host/client_session.cc b/remoting/host/client_session.cc index 1aae53c..0ebd0c5 100644 --- a/remoting/host/client_session.cc +++ b/remoting/host/client_session.cc @@ -7,6 +7,17 @@ #include "base/task.h" #include "remoting/host/user_authenticator.h" #include "remoting/proto/auth.pb.h" +#include "remoting/proto/event.pb.h" + +// The number of remote mouse events to record for the purpose of eliminating +// "echoes" detected by the local input detector. The value should be large +// enough to cope with the fact that multiple events might be injected before +// any echoes are detected. +static const unsigned int kNumRemoteMousePositions = 10; + +// The number of milliseconds for which to block remote input when local input +// is received. +static const int64 kRemoteBlockTimeoutMillis = 2000; namespace remoting { @@ -19,7 +30,8 @@ ClientSession::ClientSession( user_authenticator_(user_authenticator), connection_(connection), input_stub_(input_stub), - authenticated_(false) { + authenticated_(false), + remote_mouse_button_state_(0) { } ClientSession::~ClientSession() { @@ -78,7 +90,24 @@ void ClientSession::InjectKeyEvent(const protocol::KeyEvent* event, void ClientSession::InjectMouseEvent(const protocol::MouseEvent* event, Task* done) { base::ScopedTaskRunner done_runner(done); - if (authenticated_) { + if (authenticated_ && !ShouldIgnoreRemoteInput()) { + if (event->has_button() && event->has_button_down()) { + if (event->button() >= 1 && event->button() < 32) { + uint32 button_change = 1 << (event->button() - 1); + if (event->button_down()) { + remote_mouse_button_state_ |= button_change; + } else { + remote_mouse_button_state_ &= ~button_change; + } + } + } + if (event->has_x() && event->has_y()) { + gfx::Point pos(event->x(), event->y()); + recent_remote_mouse_positions_.push_back(pos); + if (recent_remote_mouse_positions_.size() > kNumRemoteMousePositions) { + recent_remote_mouse_positions_.pop_front(); + } + } input_stub_->InjectMouseEvent(event, done_runner.Release()); } } @@ -88,4 +117,29 @@ void ClientSession::Disconnect() { authenticated_ = false; } +void ClientSession::LocalMouseMoved(const gfx::Point& mouse_pos) { + // If this is a genuine local input event (rather than an echo of a remote + // input event that we've just injected), then ignore remote inputs for a + // short time. + if (!recent_remote_mouse_positions_.empty() && + mouse_pos == *recent_remote_mouse_positions_.begin()) { + recent_remote_mouse_positions_.pop_front(); + } else { + latest_local_input_time_ = base::Time::Now(); + } +} + +bool ClientSession::ShouldIgnoreRemoteInput() const { + // If the last remote input event was a click or a drag, then it's not safe + // to block remote input. For example, it might result in the host missing + // the mouse-up event and being stuck with the button pressed. + if (remote_mouse_button_state_ != 0) + return false; + int64 millis = (base::Time::Now() - latest_local_input_time_) + .InMilliseconds(); + if (millis < kRemoteBlockTimeoutMillis) + return true; + return false; +} + } // namespace remoting diff --git a/remoting/host/client_session.h b/remoting/host/client_session.h index ac124a8..e014bdc 100644 --- a/remoting/host/client_session.h +++ b/remoting/host/client_session.h @@ -5,9 +5,12 @@ #ifndef REMOTING_HOST_CLIENT_SESSION_H_ #define REMOTING_HOST_CLIENT_SESSION_H_ +#include <list> + #include "remoting/protocol/connection_to_client.h" #include "remoting/protocol/host_stub.h" #include "remoting/protocol/input_stub.h" +#include "ui/gfx/point.h" namespace remoting { @@ -65,6 +68,13 @@ class ClientSession : public protocol::HostStub, return authenticated_; } + // Indicate that local mouse activity has been detected. This causes remote + // inputs to be ignored for a short time so that the local user will always + // have the upper hand in 'pointer wars'. + void LocalMouseMoved(const gfx::Point& new_pos); + + bool ShouldIgnoreRemoteInput() const; + private: friend class base::RefCountedThreadSafe<ClientSession>; virtual ~ClientSession(); @@ -83,6 +93,11 @@ class ClientSession : public protocol::HostStub, // Whether this client is authenticated. bool authenticated_; + // State to control remote input blocking while the local pointer is in use. + uint32 remote_mouse_button_state_; + std::list<gfx::Point> recent_remote_mouse_positions_; + base::Time latest_local_input_time_; + DISALLOW_COPY_AND_ASSIGN(ClientSession); }; diff --git a/remoting/host/client_session_unittest.cc b/remoting/host/client_session_unittest.cc index bd6860a..c245499 100644 --- a/remoting/host/client_session_unittest.cc +++ b/remoting/host/client_session_unittest.cc @@ -113,4 +113,44 @@ TEST_F(ClientSessionTest, InputStubFilter) { client_session_->InjectMouseEvent(&mouse_event3, new DummyTask()); } +TEST_F(ClientSessionTest, LocalInputTest) { + protocol::MouseEvent mouse_event1; + mouse_event1.set_x(100); + mouse_event1.set_y(101); + protocol::MouseEvent mouse_event2; + mouse_event2.set_x(200); + mouse_event2.set_y(201); + protocol::MouseEvent mouse_event3; + mouse_event3.set_x(300); + mouse_event3.set_y(301); + + protocol::LocalLoginCredentials credentials; + credentials.set_type(protocol::PASSWORD); + credentials.set_username("user"); + credentials.set_credential("password"); + + InSequence s; + EXPECT_CALL(*user_authenticator_, Authenticate(_, _)) + .WillOnce(Return(true)); + EXPECT_CALL(session_event_handler_, LocalLoginSucceeded(_)); + EXPECT_CALL(input_stub_, InjectMouseEvent(&mouse_event1, _)); + EXPECT_CALL(input_stub_, InjectMouseEvent(&mouse_event2, _)); + EXPECT_CALL(*connection_.get(), Disconnect()); + + client_session_->BeginSessionRequest(&credentials, new DummyTask()); + // This event should get through to the input stub. + client_session_->InjectMouseEvent(&mouse_event1, new DummyTask()); + // This one should too because the local event echoes the remote one. + client_session_->LocalMouseMoved(gfx::Point(mouse_event1.x(), + mouse_event1.y())); + client_session_->InjectMouseEvent(&mouse_event2, new DummyTask()); + // This one should not. + client_session_->LocalMouseMoved(gfx::Point(mouse_event1.x(), + mouse_event1.y())); + client_session_->InjectMouseEvent(&mouse_event3, new DummyTask()); + // TODO(jamiewalch): Verify that remote inputs are re-enabled eventually + // (via dependency injection, not sleep!) + client_session_->Disconnect(); + } + } // namespace remoting diff --git a/remoting/host/desktop_environment.cc b/remoting/host/desktop_environment.cc index 8c662b2..6a2ff03 100644 --- a/remoting/host/desktop_environment.cc +++ b/remoting/host/desktop_environment.cc @@ -8,17 +8,20 @@ #include "remoting/host/curtain.h" #include "remoting/host/event_executor.h" #include "remoting/host/disconnect_window.h" +#include "remoting/host/local_input_monitor.h" namespace remoting { DesktopEnvironment::DesktopEnvironment(Capturer* capturer, EventExecutor* event_executor, Curtain* curtain, - DisconnectWindow* disconnect_window) + DisconnectWindow* disconnect_window, + LocalInputMonitor* local_input_monitor) : capturer_(capturer), event_executor_(event_executor), curtain_(curtain), - disconnect_window_(disconnect_window) { + disconnect_window_(disconnect_window), + local_input_monitor_(local_input_monitor) { } DesktopEnvironment::~DesktopEnvironment() { diff --git a/remoting/host/desktop_environment.h b/remoting/host/desktop_environment.h index 1ca119b..4182645 100644 --- a/remoting/host/desktop_environment.h +++ b/remoting/host/desktop_environment.h @@ -12,20 +12,25 @@ namespace remoting { class Capturer; class Curtain; -class EventExecutor; class DisconnectWindow; +class EventExecutor; +class LocalInputMonitor; class DesktopEnvironment { public: // DesktopEnvironment takes ownership of all the objects passed the ctor. DesktopEnvironment(Capturer* capturer, EventExecutor* event_executor, - Curtain* curtain, DisconnectWindow* disconnect_window); + Curtain* curtain, DisconnectWindow* disconnect_window, + LocalInputMonitor* monitor); virtual ~DesktopEnvironment(); Capturer* capturer() const { return capturer_.get(); } EventExecutor* event_executor() const { return event_executor_.get(); } Curtain* curtain() const { return curtain_.get(); } DisconnectWindow* disconnect_window() { return disconnect_window_.get(); } + LocalInputMonitor* local_input_monitor() { + return local_input_monitor_.get(); + } private: // Capturer to be used by ScreenRecorder. @@ -40,6 +45,10 @@ class DesktopEnvironment { // Provide a user interface allowing the host user to close the connection. scoped_ptr<DisconnectWindow> disconnect_window_; + // Monitor local inputs to allow remote inputs to be blocked while the local + // user is trying to do something. + scoped_ptr<LocalInputMonitor> local_input_monitor_; + DISALLOW_COPY_AND_ASSIGN(DesktopEnvironment); }; diff --git a/remoting/host/host_mock_objects.cc b/remoting/host/host_mock_objects.cc index ccc5982..c3bc0ed 100644 --- a/remoting/host/host_mock_objects.cc +++ b/remoting/host/host_mock_objects.cc @@ -14,14 +14,14 @@ MockCurtain::MockCurtain() {} MockCurtain::~MockCurtain() {} -MockEventExecutor::MockEventExecutor() {} - -MockEventExecutor::~MockEventExecutor() {} - Curtain* Curtain::Create() { return new MockCurtain(); } +MockEventExecutor::MockEventExecutor() {} + +MockEventExecutor::~MockEventExecutor() {} + MockDisconnectWindow::MockDisconnectWindow() {} MockDisconnectWindow::~MockDisconnectWindow() {} @@ -30,6 +30,14 @@ DisconnectWindow* DisconnectWindow::Create() { return new MockDisconnectWindow(); } +MockLocalInputMonitor::MockLocalInputMonitor() {} + +MockLocalInputMonitor::~MockLocalInputMonitor() {} + +LocalInputMonitor* LocalInputMonitor::Create() { + return new MockLocalInputMonitor(); +} + MockChromotingHostContext::MockChromotingHostContext() {} MockChromotingHostContext::~MockChromotingHostContext() {} diff --git a/remoting/host/host_mock_objects.h b/remoting/host/host_mock_objects.h index 5a8264b..c4090d3 100644 --- a/remoting/host/host_mock_objects.h +++ b/remoting/host/host_mock_objects.h @@ -12,6 +12,7 @@ #include "remoting/host/client_session.h" #include "remoting/host/disconnect_window.h" #include "remoting/host/event_executor.h" +#include "remoting/host/local_input_monitor.h" #include "remoting/host/user_authenticator.h" #include "testing/gmock/include/gmock/gmock.h" @@ -53,6 +54,15 @@ class MockDisconnectWindow : public DisconnectWindow { MOCK_METHOD0(Hide, void()); }; +class MockLocalInputMonitor : public LocalInputMonitor { + public: + MockLocalInputMonitor(); + virtual ~MockLocalInputMonitor(); + + MOCK_METHOD1(Start, void(remoting::ChromotingHost* host)); + MOCK_METHOD0(Stop, void()); +}; + class MockChromotingHostContext : public ChromotingHostContext { public: MockChromotingHostContext(); @@ -81,7 +91,7 @@ class MockClientSessionEventHandler : public ClientSession::EventHandler { void(scoped_refptr<protocol::ConnectionToClient>)); private: - DISALLOW_COPY_AND_ASSIGN(MockClientSessionEventHandler); + DISALLOW_COPY_AND_ASSIGN(MockClientSessionEventHandler); }; class MockEventExecutor : public EventExecutor { diff --git a/remoting/host/local_input_monitor.h b/remoting/host/local_input_monitor.h new file mode 100644 index 0000000..7c2b9d5 --- /dev/null +++ b/remoting/host/local_input_monitor.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef REMOTING_LOCAL_INPUT_MONITOR_H_ +#define REMOTING_LOCAL_INPUT_MONITOR_H_ + +namespace remoting { + +class ChromotingHost; + +class LocalInputMonitor { + public: + virtual ~LocalInputMonitor() {} + + virtual void Start(ChromotingHost* host) = 0; + virtual void Stop() = 0; + + static LocalInputMonitor* Create(); +}; + +} // namespace remoting + +#endif diff --git a/remoting/host/local_input_monitor_linux.cc b/remoting/host/local_input_monitor_linux.cc new file mode 100644 index 0000000..c6e8037 --- /dev/null +++ b/remoting/host/local_input_monitor_linux.cc @@ -0,0 +1,52 @@ +// 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. + +#include "remoting/host/local_input_monitor.h" +#include "remoting/host/local_input_monitor_thread_linux.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" + +namespace { + +class LocalInputMonitorLinux : public remoting::LocalInputMonitor { + public: + LocalInputMonitorLinux(); + ~LocalInputMonitorLinux(); + + virtual void Start(remoting::ChromotingHost* host) OVERRIDE; + virtual void Stop() OVERRIDE; + + private: + remoting::LocalInputMonitorThread* thread_; +}; + +} // namespace + + +LocalInputMonitorLinux::LocalInputMonitorLinux() + : thread_(NULL) { +} + +LocalInputMonitorLinux::~LocalInputMonitorLinux() { + CHECK(!thread_); +} + +void LocalInputMonitorLinux::Start(remoting::ChromotingHost* host) { + CHECK(!thread_); + thread_ = new remoting::LocalInputMonitorThread(host); + thread_->Start(); +} + +void LocalInputMonitorLinux::Stop() { + CHECK(thread_); + thread_->Stop(); + thread_->Join(); + delete thread_; + thread_ = 0; +} + +remoting::LocalInputMonitor* remoting::LocalInputMonitor::Create() { + return new LocalInputMonitorLinux; +} diff --git a/remoting/host/local_input_monitor_mac.cc b/remoting/host/local_input_monitor_mac.cc new file mode 100644 index 0000000..5ee3c01 --- /dev/null +++ b/remoting/host/local_input_monitor_mac.cc @@ -0,0 +1,34 @@ +// 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. + +#include "remoting/host/local_input_monitor.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" + +namespace { + +class LocalInputMonitorMac : public remoting::LocalInputMonitor { + public: + LocalInputMonitorMac() {} + virtual void Start(remoting::ChromotingHost* host) OVERRIDE; + virtual void Stop() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorMac); +}; + +} // namespace + +void LocalInputMonitorMac::Start(remoting::ChromotingHost* host) { + NOTIMPLEMENTED(); +} + +void LocalInputMonitorMac::Stop() { + NOTIMPLEMENTED(); +} + +remoting::LocalInputMonitor* remoting::LocalInputMonitor::Create() { + return new LocalInputMonitorMac; +} diff --git a/remoting/host/local_input_monitor_thread_linux.cc b/remoting/host/local_input_monitor_thread_linux.cc new file mode 100644 index 0000000..98fb92c --- /dev/null +++ b/remoting/host/local_input_monitor_thread_linux.cc @@ -0,0 +1,141 @@ +// 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. + +#include "remoting/host/local_input_monitor_thread_linux.h" +#include "remoting/host/chromoting_host.h" + +#include <sys/select.h> +#include <unistd.h> +#include <X11/Xlibint.h> +#include <X11/extensions/record.h> + +#include "base/eintr_wrapper.h" + +namespace { + +struct scoped_x_record_context { + scoped_x_record_context() + : control_channel(NULL), data_channel(NULL), range(NULL), context(0) {} + ~scoped_x_record_context() { + if (range) + XFree(range); + if (context) + XRecordFreeContext(data_channel, context); + if (data_channel) + XCloseDisplay(data_channel); + if (control_channel) + XCloseDisplay(control_channel); + } + Display* control_channel; + Display* data_channel; + XRecordRange* range; + XRecordContext context; +}; + +} // namespace + + +namespace remoting { + +static void ProcessReply(XPointer host, + XRecordInterceptData* data) { + if (data->category == XRecordFromServer) { + xEvent* event = reinterpret_cast<xEvent*>(data->data); + gfx::Point pos(event->u.keyButtonPointer.rootX, + event->u.keyButtonPointer.rootY); + reinterpret_cast<ChromotingHost*>(host)->LocalMouseMoved(pos); + } + XRecordFreeData(data); +} + +LocalInputMonitorThread::LocalInputMonitorThread(ChromotingHost* host) + : base::SimpleThread("LocalInputMonitor"), + host_(host) { + wakeup_pipe_[0] = -1; + wakeup_pipe_[1] = -1; + CHECK_EQ(pipe(wakeup_pipe_), 0); +} + +LocalInputMonitorThread::~LocalInputMonitorThread() { + close(wakeup_pipe_[0]); + close(wakeup_pipe_[1]); +} + +void LocalInputMonitorThread::Stop() { + if (HANDLE_EINTR(write(wakeup_pipe_[1], "", 1)) != 1) { + NOTREACHED() << "Could not write to the local input monitor wakeup pipe!"; + } +} + +void LocalInputMonitorThread::Run() { + // TODO(jamiewalch): For now, just don't run the thread if the wakeup pipe + // could not be created. As part of the task of cleaning up the dis/connect + // actions, this should be treated as an initialization failure. + if (wakeup_pipe_[0] == -1) + return; + + scoped_x_record_context scoper; + + // TODO(jamiewalch): We should pass the display in. At that point, since + // XRecord needs a private connection to the X Server for its data channel, + // we'll need something like the following: + // data_channel = XOpenDisplay(DisplayString(control_channel)); + scoper.data_channel = XOpenDisplay(NULL); + scoper.control_channel = XOpenDisplay(NULL); + if (!scoper.data_channel || !scoper.control_channel) { + LOG(ERROR) << "Couldn't open X display"; + return; + } + + int xr_opcode, xr_event, xr_error; + if (!XQueryExtension(scoper.control_channel, "RECORD", + &xr_opcode, &xr_event, &xr_error)) { + LOG(ERROR) << "X Record extension not available."; + return; + } + + scoper.range = XRecordAllocRange(); + if (!scoper.range) { + LOG(ERROR) << "XRecordAllocRange failed."; + return; + } + scoper.range->device_events.first = MotionNotify; + scoper.range->device_events.last = MotionNotify; + XRecordClientSpec client_spec = XRecordAllClients; + + scoper.context = XRecordCreateContext(scoper.data_channel, 0, &client_spec, 1, + &scoper.range, 1); + if (!scoper.context) { + LOG(ERROR) << "XRecordCreateContext failed."; + return; + } + + if (!XRecordEnableContextAsync(scoper.data_channel, scoper.context, + ProcessReply, + reinterpret_cast<XPointer>(host_))) { + LOG(ERROR) << "XRecordEnableContextAsync failed."; + return; + } + + bool stopped = false; + while (!stopped) { + while (XPending(scoper.data_channel)) { + XEvent ev; + XNextEvent(scoper.data_channel, &ev); + } + fd_set read_fs; + FD_ZERO(&read_fs); + FD_SET(ConnectionNumber(scoper.data_channel), &read_fs); + FD_SET(wakeup_pipe_[0], &read_fs); + select(FD_SETSIZE, &read_fs, NULL, NULL, NULL); + stopped = FD_ISSET(wakeup_pipe_[0], &read_fs); + } + + // Context must be disabled via the control channel because we can't send any + // X protocol traffic over the data channel while it's recording. + XRecordDisableContext(scoper.control_channel, scoper.context); + XFlush(scoper.control_channel); +} + +} // namespace remoting diff --git a/remoting/host/local_input_monitor_thread_linux.h b/remoting/host/local_input_monitor_thread_linux.h new file mode 100644 index 0000000..3fec5f0 --- /dev/null +++ b/remoting/host/local_input_monitor_thread_linux.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef LOCAL_INPUT_MONITOR_THREAD_LINUX_H_ +#define LOCAL_INPUT_MONITOR_THREAD_LINUX_H_ + +#include "base/threading/simple_thread.h" + +namespace remoting { + +class ChromotingHost; + +class LocalInputMonitorThread : public base::SimpleThread { + public: + explicit LocalInputMonitorThread(ChromotingHost* host); + virtual ~LocalInputMonitorThread(); + + void Stop(); + virtual void Run(); + + private: + ChromotingHost* host_; + int wakeup_pipe_[2]; + + DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorThread); +}; + +} // namespace remoting + +#endif diff --git a/remoting/host/local_input_monitor_win.cc b/remoting/host/local_input_monitor_win.cc new file mode 100644 index 0000000..6d06bc09 --- /dev/null +++ b/remoting/host/local_input_monitor_win.cc @@ -0,0 +1,34 @@ +// 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. + +#include "remoting/host/local_input_monitor.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" + +namespace { + +class LocalInputMonitorWin : public remoting::LocalInputMonitor { + public: + LocalInputMonitorWin() {} + virtual void Start(remoting::ChromotingHost* host) OVERRIDE; + virtual void Stop() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorWin); +}; + +} // namespace + +void LocalInputMonitorWin::Start(remoting::ChromotingHost* host) { + NOTIMPLEMENTED(); +} + +void LocalInputMonitorWin::Stop() { + NOTIMPLEMENTED(); +} + +remoting::LocalInputMonitor* remoting::LocalInputMonitor::Create() { + return new LocalInputMonitorWin; +} diff --git a/remoting/host/simple_host_process.cc b/remoting/host/simple_host_process.cc index 368ea40..0727461 100644 --- a/remoting/host/simple_host_process.cc +++ b/remoting/host/simple_host_process.cc @@ -42,6 +42,7 @@ #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" @@ -174,10 +175,12 @@ class SimpleHost { remoting::Curtain* curtain = remoting::Curtain::Create(); remoting::DisconnectWindow* disconnect_window = remoting::DisconnectWindow::Create(); + remoting::LocalInputMonitor* local_input_monitor = + remoting::LocalInputMonitor::Create(); host = ChromotingHost::Create( &context, config, new DesktopEnvironment(capturer, event_executor, curtain, - disconnect_window), + disconnect_window, local_input_monitor), access_verifier.release()); } else { host = ChromotingHost::Create(&context, config, diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index e75f6ae..6f75dbf 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -365,6 +365,10 @@ 'host/in_memory_host_config.h', 'host/json_host_config.cc', 'host/json_host_config.h', + 'host/local_input_monitor_linux.cc', + 'host/local_input_monitor_mac.cc', + 'host/local_input_monitor_thread_linux.cc', + 'host/local_input_monitor_win.cc', 'host/register_support_host_request.cc', 'host/register_support_host_request.h', 'host/self_access_verifier.cc', |