Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(490)

Side by Side Diff: remoting/host/client_session.cc

Issue 9465035: Move ClientSession's input logic into separate components. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase. Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « remoting/host/client_session.h ('k') | remoting/host/client_session_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "remoting/host/client_session.h" 5 #include "remoting/host/client_session.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "base/message_loop_proxy.h" 9 #include "base/message_loop_proxy.h"
10 #include "remoting/host/capturer.h" 10 #include "remoting/host/capturer.h"
11 #include "remoting/proto/event.pb.h" 11 #include "remoting/proto/event.pb.h"
12 12
13 // The number of remote mouse events to record for the purpose of eliminating
14 // "echoes" detected by the local input detector. The value should be large
15 // enough to cope with the fact that multiple events might be injected before
16 // any echoes are detected.
17 static const unsigned int kNumRemoteMousePositions = 50;
18
19 // The number of milliseconds for which to block remote input when local input
20 // is received.
21 static const int64 kRemoteBlockTimeoutMillis = 2000;
22
23 namespace remoting { 13 namespace remoting {
24 14
25 using protocol::KeyEvent; 15 using protocol::KeyEvent;
26 using protocol::MouseEvent; 16 using protocol::MouseEvent;
27 17
28 ClientSession::ClientSession( 18 ClientSession::ClientSession(
29 EventHandler* event_handler, 19 EventHandler* event_handler,
30 scoped_ptr<protocol::ConnectionToClient> connection, 20 scoped_ptr<protocol::ConnectionToClient> connection,
31 protocol::HostEventStub* host_event_stub, 21 protocol::HostEventStub* host_event_stub,
32 Capturer* capturer) 22 Capturer* capturer)
33 : event_handler_(event_handler), 23 : event_handler_(event_handler),
34 connection_(connection.Pass()), 24 connection_(connection.Pass()),
35 client_jid_(connection_->session()->jid()), 25 client_jid_(connection_->session()->jid()),
36 host_event_stub_(host_event_stub), 26 host_event_stub_(host_event_stub),
37 capturer_(capturer), 27 input_tracker_(host_event_stub),
38 authenticated_(false), 28 remote_input_filter_(&input_tracker_),
39 connected_(false), 29 capturer_(capturer) {
40 awaiting_continue_approval_(false),
41 remote_mouse_button_state_(0) {
42 connection_->SetEventHandler(this); 30 connection_->SetEventHandler(this);
43 31
44 // TODO(sergeyu): Currently ConnectionToClient expects stubs to be 32 // TODO(sergeyu): Currently ConnectionToClient expects stubs to be
45 // set before channels are connected. Make it possible to set stubs 33 // set before channels are connected. Make it possible to set stubs
46 // later and set them only when connection is authenticated. 34 // later and set them only when connection is authenticated.
47 connection_->set_clipboard_stub(this); 35 connection_->set_clipboard_stub(this);
48 connection_->set_host_stub(this); 36 connection_->set_host_stub(this);
49 connection_->set_input_stub(this); 37 connection_->set_input_stub(&auth_input_filter_);
50 } 38 }
51 39
52 ClientSession::~ClientSession() { 40 ClientSession::~ClientSession() {
53 } 41 }
54 42
55 void ClientSession::InjectClipboardEvent( 43 void ClientSession::InjectClipboardEvent(
56 const protocol::ClipboardEvent& event) { 44 const protocol::ClipboardEvent& event) {
57 DCHECK(CalledOnValidThread()); 45 DCHECK(CalledOnValidThread());
58 46
59 if (connected_) { 47 // TODO(wez): Disable clipboard in both directions on local activity, and
60 host_event_stub_->InjectClipboardEvent(event); 48 // replace these tests with a HostInputFilter (or ClipboardFilter).
61 } 49 if (auth_input_filter_.input_stub() == NULL)
50 return;
51 if (disable_input_filter_.input_stub() == NULL)
52 return;
53
54 host_event_stub_->InjectClipboardEvent(event);
62 } 55 }
63 56
64 void ClientSession::InjectKeyEvent(const KeyEvent& event) { 57 void ClientSession::InjectKeyEvent(const KeyEvent& event) {
65 DCHECK(CalledOnValidThread()); 58 DCHECK(CalledOnValidThread());
66 59 auth_input_filter_.InjectKeyEvent(event);
67 if (connected_ && !ShouldIgnoreRemoteKeyboardInput(event)) {
68 RecordKeyEvent(event);
69 host_event_stub_->InjectKeyEvent(event);
70 }
71 } 60 }
72 61
73 void ClientSession::InjectMouseEvent(const MouseEvent& event) { 62 void ClientSession::InjectMouseEvent(const MouseEvent& event) {
74 DCHECK(CalledOnValidThread()); 63 DCHECK(CalledOnValidThread());
75 64
76 if (connected_ && !ShouldIgnoreRemoteMouseInput(event)) { 65 MouseEvent event_to_inject = event;
77 RecordMouseButtonState(event); 66 if (event.has_x() && event.has_y()) {
78 MouseEvent event_to_inject = event; 67 // In case the client sends events with off-screen coordinates, modify
79 if (event.has_x() && event.has_y()) { 68 // the event to lie within the current screen area. This is better than
80 // In case the client sends events with off-screen coordinates, modify 69 // simply discarding the event, which might lose a button-up event at the
81 // the event to lie within the current screen area. This is better than 70 // end of a drag'n'drop (or cause other related problems).
82 // simply discarding the event, which might lose a button-up event at the 71 SkIPoint pos(SkIPoint::Make(event.x(), event.y()));
83 // end of a drag'n'drop (or cause other related problems). 72 const SkISize& screen = capturer_->size_most_recent();
84 SkIPoint pos(SkIPoint::Make(event.x(), event.y())); 73 pos.setX(std::max(0, std::min(screen.width() - 1, pos.x())));
85 const SkISize& screen = capturer_->size_most_recent(); 74 pos.setY(std::max(0, std::min(screen.height() - 1, pos.y())));
86 pos.setX(std::max(0, std::min(screen.width() - 1, pos.x()))); 75 event_to_inject.set_x(pos.x());
87 pos.setY(std::max(0, std::min(screen.height() - 1, pos.y()))); 76 event_to_inject.set_y(pos.y());
88 event_to_inject.set_x(pos.x());
89 event_to_inject.set_y(pos.y());
90
91 // Record the mouse position so we can use it if we need to inject
92 // fake mouse button events. Note that we need to do this after we
93 // clamp the values to the screen area.
94 remote_mouse_pos_ = pos;
95
96 injected_mouse_positions_.push_back(pos);
97 if (injected_mouse_positions_.size() > kNumRemoteMousePositions) {
98 VLOG(1) << "Injected mouse positions queue full.";
99 injected_mouse_positions_.pop_front();
100 }
101 }
102 host_event_stub_->InjectMouseEvent(event_to_inject);
103 } 77 }
78 auth_input_filter_.InjectMouseEvent(event_to_inject);
104 } 79 }
105 80
106 void ClientSession::OnConnectionAuthenticated( 81 void ClientSession::OnConnectionAuthenticated(
107 protocol::ConnectionToClient* connection) { 82 protocol::ConnectionToClient* connection) {
108 authenticated_ = true; 83 DCHECK(CalledOnValidThread());
84 DCHECK_EQ(connection_.get(), connection);
85 auth_input_filter_.set_input_stub(&disable_input_filter_);
109 event_handler_->OnSessionAuthenticated(this); 86 event_handler_->OnSessionAuthenticated(this);
110 } 87 }
111 88
112 void ClientSession::OnConnectionChannelsConnected( 89 void ClientSession::OnConnectionChannelsConnected(
113 protocol::ConnectionToClient* connection) { 90 protocol::ConnectionToClient* connection) {
114 DCHECK(CalledOnValidThread()); 91 DCHECK(CalledOnValidThread());
115 DCHECK_EQ(connection_.get(), connection); 92 DCHECK_EQ(connection_.get(), connection);
116 connected_ = true; 93 SetDisableInputs(false);
117 event_handler_->OnSessionChannelsConnected(this); 94 event_handler_->OnSessionChannelsConnected(this);
118 } 95 }
119 96
120 void ClientSession::OnConnectionClosed( 97 void ClientSession::OnConnectionClosed(
121 protocol::ConnectionToClient* connection, 98 protocol::ConnectionToClient* connection,
122 protocol::ErrorCode error) { 99 protocol::ErrorCode error) {
123 DCHECK(CalledOnValidThread()); 100 DCHECK(CalledOnValidThread());
124 DCHECK_EQ(connection_.get(), connection); 101 DCHECK_EQ(connection_.get(), connection);
125 if (!authenticated_) 102 if (!auth_input_filter_.input_stub())
126 event_handler_->OnSessionAuthenticationFailed(this); 103 event_handler_->OnSessionAuthenticationFailed(this);
104 auth_input_filter_.set_input_stub(NULL);
105
106 // Ensure that any pressed keys or buttons are released.
107 input_tracker_.ReleaseAll();
108
127 // TODO(sergeyu): Log failure reason? 109 // TODO(sergeyu): Log failure reason?
128 event_handler_->OnSessionClosed(this); 110 event_handler_->OnSessionClosed(this);
129 } 111 }
130 112
131 void ClientSession::OnSequenceNumberUpdated( 113 void ClientSession::OnSequenceNumberUpdated(
132 protocol::ConnectionToClient* connection, int64 sequence_number) { 114 protocol::ConnectionToClient* connection, int64 sequence_number) {
133 DCHECK(CalledOnValidThread()); 115 DCHECK(CalledOnValidThread());
134 DCHECK_EQ(connection_.get(), connection); 116 DCHECK_EQ(connection_.get(), connection);
135 event_handler_->OnSessionSequenceNumber(this, sequence_number); 117 event_handler_->OnSessionSequenceNumber(this, sequence_number);
136 } 118 }
137 119
138 void ClientSession::OnRouteChange( 120 void ClientSession::OnRouteChange(
139 protocol::ConnectionToClient* connection, 121 protocol::ConnectionToClient* connection,
140 const std::string& channel_name, 122 const std::string& channel_name,
141 const protocol::TransportRoute& route) { 123 const protocol::TransportRoute& route) {
142 DCHECK(CalledOnValidThread()); 124 DCHECK(CalledOnValidThread());
143 DCHECK_EQ(connection_.get(), connection); 125 DCHECK_EQ(connection_.get(), connection);
144 event_handler_->OnSessionRouteChange(this, channel_name, route); 126 event_handler_->OnSessionRouteChange(this, channel_name, route);
145 } 127 }
146 128
147 void ClientSession::Disconnect() { 129 void ClientSession::Disconnect() {
148 DCHECK(CalledOnValidThread()); 130 DCHECK(CalledOnValidThread());
149 DCHECK(connection_.get()); 131 DCHECK(connection_.get());
150 connected_ = false;
151 RestoreEventState();
152 132
153 // This triggers OnSessionClosed() and the session may be destroyed 133 // This triggers OnConnectionClosed(), and the session may be destroyed
154 // as the result, so this call must be the last in this method. 134 // as the result, so this call must be the last in this method.
155 connection_->Disconnect(); 135 connection_->Disconnect();
156 } 136 }
157 137
158 void ClientSession::LocalMouseMoved(const SkIPoint& mouse_pos) { 138 void ClientSession::LocalMouseMoved(const SkIPoint& mouse_pos) {
159 DCHECK(CalledOnValidThread()); 139 DCHECK(CalledOnValidThread());
140 remote_input_filter_.LocalMouseMoved(mouse_pos);
141 }
160 142
161 // If this is a genuine local input event (rather than an echo of a remote 143 void ClientSession::SetDisableInputs(bool disable_inputs) {
162 // input event that we've just injected), then ignore remote inputs for a 144 DCHECK(CalledOnValidThread());
163 // short time. 145
164 std::list<SkIPoint>::iterator found_position = 146 if (disable_inputs) {
165 std::find(injected_mouse_positions_.begin(), 147 disable_input_filter_.set_input_stub(NULL);
166 injected_mouse_positions_.end(), mouse_pos); 148 input_tracker_.ReleaseAll();
167 if (found_position != injected_mouse_positions_.end()) {
168 // Remove it from the list, and any positions that were added before it,
169 // if any. This is because the local input monitor is assumed to receive
170 // injected mouse position events in the order in which they were injected
171 // (if at all). If the position is found somewhere other than the front of
172 // the queue, this would be because the earlier positions weren't
173 // successfully injected (or the local input monitor might have skipped over
174 // some positions), and not because the events were out-of-sequence. These
175 // spurious positions should therefore be discarded.
176 injected_mouse_positions_.erase(injected_mouse_positions_.begin(),
177 ++found_position);
178 } else { 149 } else {
179 latest_local_input_time_ = base::Time::Now(); 150 disable_input_filter_.set_input_stub(&remote_input_filter_);
180 } 151 }
181 } 152 }
182 153
183 bool ClientSession::ShouldIgnoreRemoteMouseInput(
184 const protocol::MouseEvent& event) const {
185 DCHECK(CalledOnValidThread());
186
187 // If the last remote input event was a click or a drag, then it's not safe
188 // to block remote mouse events. For example, it might result in the host
189 // missing the mouse-up event and being stuck with the button pressed.
190 if (remote_mouse_button_state_ != 0)
191 return false;
192 // Otherwise, if the host user has not yet approved the continuation of the
193 // connection, then ignore remote mouse events.
194 if (awaiting_continue_approval_)
195 return true;
196 // Otherwise, ignore remote mouse events if the local mouse moved recently.
197 int64 millis = (base::Time::Now() - latest_local_input_time_)
198 .InMilliseconds();
199 if (millis < kRemoteBlockTimeoutMillis)
200 return true;
201 return false;
202 }
203
204 bool ClientSession::ShouldIgnoreRemoteKeyboardInput(
205 const KeyEvent& event) const {
206 DCHECK(CalledOnValidThread());
207
208 // If the host user has not yet approved the continuation of the connection,
209 // then all remote keyboard input is ignored, except to release keys that
210 // were already pressed.
211 if (awaiting_continue_approval_) {
212 return event.pressed() ||
213 (pressed_keys_.find(event.keycode()) == pressed_keys_.end());
214 }
215 return false;
216 }
217
218 void ClientSession::RecordKeyEvent(const KeyEvent& event) {
219 DCHECK(CalledOnValidThread());
220
221 if (event.pressed()) {
222 pressed_keys_.insert(event.keycode());
223 } else {
224 pressed_keys_.erase(event.keycode());
225 }
226 }
227
228 void ClientSession::RecordMouseButtonState(const MouseEvent& event) {
229 DCHECK(CalledOnValidThread());
230
231 if (event.has_button() && event.has_button_down()) {
232 // Button values are defined in remoting/proto/event.proto.
233 if (event.button() >= 1 && event.button() < MouseEvent::BUTTON_MAX) {
234 uint32 button_change = 1 << (event.button() - 1);
235 if (event.button_down()) {
236 remote_mouse_button_state_ |= button_change;
237 } else {
238 remote_mouse_button_state_ &= ~button_change;
239 }
240 }
241 }
242 }
243
244 void ClientSession::RestoreEventState() {
245 DCHECK(CalledOnValidThread());
246
247 // Undo any currently pressed keys.
248 std::set<int>::iterator i;
249 for (i = pressed_keys_.begin(); i != pressed_keys_.end(); ++i) {
250 KeyEvent key;
251 key.set_keycode(*i);
252 key.set_pressed(false);
253 host_event_stub_->InjectKeyEvent(key);
254 }
255 pressed_keys_.clear();
256
257 // Undo any currently pressed mouse buttons.
258 for (int i = 1; i < MouseEvent::BUTTON_MAX; i++) {
259 if (remote_mouse_button_state_ & (1 << (i - 1))) {
260 MouseEvent mouse;
261 // TODO(wez): Shouldn't [need to] set position here.
262 mouse.set_x(remote_mouse_pos_.x());
263 mouse.set_y(remote_mouse_pos_.y());
264 mouse.set_button((MouseEvent::MouseButton)i);
265 mouse.set_button_down(false);
266 host_event_stub_->InjectMouseEvent(mouse);
267 }
268 }
269 remote_mouse_button_state_ = 0;
270 }
271
272 } // namespace remoting 154 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/client_session.h ('k') | remoting/host/client_session_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698