summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-13 22:42:38 +0000
committerjamiewalch@google.com <jamiewalch@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2011-06-13 22:42:38 +0000
commitc78669c954f21f73734a1a484943d19525a58a5b (patch)
tree5a1ddd659dd13d1296692694d78e8b03fd9939ab
parent244d7c5866e4c7406251a499272abd76204fa005 (diff)
downloadchromium_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.cc38
-rw-r--r--remoting/host/chromoting_host.h8
-rw-r--r--remoting/host/chromoting_host_unittest.cc20
-rw-r--r--remoting/host/client_session.cc58
-rw-r--r--remoting/host/client_session.h15
-rw-r--r--remoting/host/client_session_unittest.cc40
-rw-r--r--remoting/host/desktop_environment.cc7
-rw-r--r--remoting/host/desktop_environment.h13
-rw-r--r--remoting/host/host_mock_objects.cc16
-rw-r--r--remoting/host/host_mock_objects.h12
-rw-r--r--remoting/host/local_input_monitor.h24
-rw-r--r--remoting/host/local_input_monitor_linux.cc52
-rw-r--r--remoting/host/local_input_monitor_mac.cc34
-rw-r--r--remoting/host/local_input_monitor_thread_linux.cc141
-rw-r--r--remoting/host/local_input_monitor_thread_linux.h31
-rw-r--r--remoting/host/local_input_monitor_win.cc34
-rw-r--r--remoting/host/simple_host_process.cc5
-rw-r--r--remoting/remoting.gyp4
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',