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

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

Issue 12760012: Rename EventExecutor to InputInjector. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Replace some missed occurrences and remove unused include. Created 7 years, 9 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/event_executor.h ('k') | remoting/host/event_executor_mac.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "remoting/host/event_executor.h"
6
7 #include <X11/Xlib.h>
8 #include <X11/extensions/XTest.h>
9 #include <X11/extensions/XInput.h>
10
11 #include <set>
12
13 #include "base/basictypes.h"
14 #include "base/bind.h"
15 #include "base/compiler_specific.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/single_thread_task_runner.h"
19 #include "remoting/host/clipboard.h"
20 #include "remoting/proto/internal.pb.h"
21 #include "third_party/skia/include/core/SkPoint.h"
22
23 namespace remoting {
24
25 namespace {
26
27 using protocol::ClipboardEvent;
28 using protocol::KeyEvent;
29 using protocol::MouseEvent;
30
31 // USB to XKB keycode map table.
32 #define USB_KEYMAP(usb, xkb, win, mac) {usb, xkb}
33 #include "ui/base/keycodes/usb_keycode_map.h"
34 #undef USB_KEYMAP
35
36 // Pixel-to-wheel-ticks conversion ratio used by GTK.
37 // From Source/WebKit/chromium/src/gtk/WebInputFactory.cc.
38 const float kWheelTicksPerPixel = 3.0f / 160.0f;
39
40 // A class to generate events on Linux.
41 class EventExecutorLinux : public EventExecutor {
42 public:
43 explicit EventExecutorLinux(
44 scoped_refptr<base::SingleThreadTaskRunner> task_runner);
45 virtual ~EventExecutorLinux();
46
47 bool Init();
48
49 // Clipboard stub interface.
50 virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
51
52 // InputStub interface.
53 virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
54 virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
55
56 // EventExecutor interface.
57 virtual void Start(
58 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
59
60 private:
61 // The actual implementation resides in EventExecutorLinux::Core class.
62 class Core : public base::RefCountedThreadSafe<Core> {
63 public:
64 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
65
66 bool Init();
67
68 // Mirrors the ClipboardStub interface.
69 void InjectClipboardEvent(const ClipboardEvent& event);
70
71 // Mirrors the InputStub interface.
72 void InjectKeyEvent(const KeyEvent& event);
73 void InjectMouseEvent(const MouseEvent& event);
74
75 // Mirrors the EventExecutor interface.
76 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
77
78 void Stop();
79
80 private:
81 friend class base::RefCountedThreadSafe<Core>;
82 virtual ~Core();
83
84 void InitClipboard();
85
86 // Queries whether keyboard auto-repeat is globally enabled. This is used
87 // to decide whether to temporarily disable then restore this setting. If
88 // auto-repeat has already been disabled, this class should leave it
89 // untouched.
90 bool IsAutoRepeatEnabled();
91
92 // Enables or disables keyboard auto-repeat globally.
93 void SetAutoRepeatEnabled(bool enabled);
94
95 void InjectScrollWheelClicks(int button, int count);
96 // Compensates for global button mappings and resets the XTest device
97 // mapping.
98 void InitMouseButtonMap();
99 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button);
100 int HorizontalScrollWheelToX11ButtonNumber(int dx);
101 int VerticalScrollWheelToX11ButtonNumber(int dy);
102
103 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
104
105 std::set<int> pressed_keys_;
106 SkIPoint latest_mouse_position_;
107 float wheel_ticks_x_;
108 float wheel_ticks_y_;
109
110 // X11 graphics context.
111 Display* display_;
112 Window root_window_;
113
114 int test_event_base_;
115 int test_error_base_;
116
117 // Number of buttons we support.
118 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right.
119 static const int kNumPointerButtons = 7;
120
121 int pointer_button_map_[kNumPointerButtons];
122
123 scoped_ptr<Clipboard> clipboard_;
124
125 bool saved_auto_repeat_enabled_;
126
127 DISALLOW_COPY_AND_ASSIGN(Core);
128 };
129
130 scoped_refptr<Core> core_;
131
132 DISALLOW_COPY_AND_ASSIGN(EventExecutorLinux);
133 };
134
135 EventExecutorLinux::EventExecutorLinux(
136 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
137 core_ = new Core(task_runner);
138 }
139 EventExecutorLinux::~EventExecutorLinux() {
140 core_->Stop();
141 }
142
143 bool EventExecutorLinux::Init() {
144 return core_->Init();
145 }
146
147 void EventExecutorLinux::InjectClipboardEvent(const ClipboardEvent& event) {
148 core_->InjectClipboardEvent(event);
149 }
150
151 void EventExecutorLinux::InjectKeyEvent(const KeyEvent& event) {
152 core_->InjectKeyEvent(event);
153 }
154
155 void EventExecutorLinux::InjectMouseEvent(const MouseEvent& event) {
156 core_->InjectMouseEvent(event);
157 }
158
159 void EventExecutorLinux::Start(
160 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
161 core_->Start(client_clipboard.Pass());
162 }
163
164 EventExecutorLinux::Core::Core(
165 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
166 : task_runner_(task_runner),
167 latest_mouse_position_(SkIPoint::Make(-1, -1)),
168 wheel_ticks_x_(0.0f),
169 wheel_ticks_y_(0.0f),
170 display_(XOpenDisplay(NULL)),
171 root_window_(BadValue),
172 saved_auto_repeat_enabled_(false) {
173 }
174
175 bool EventExecutorLinux::Core::Init() {
176 CHECK(display_);
177
178 if (!task_runner_->BelongsToCurrentThread())
179 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this));
180
181 root_window_ = RootWindow(display_, DefaultScreen(display_));
182 if (root_window_ == BadValue) {
183 LOG(ERROR) << "Unable to get the root window";
184 return false;
185 }
186
187 // TODO(ajwong): Do we want to check the major/minor version at all for XTest?
188 int major = 0;
189 int minor = 0;
190 if (!XTestQueryExtension(display_, &test_event_base_, &test_error_base_,
191 &major, &minor)) {
192 LOG(ERROR) << "Server does not support XTest.";
193 return false;
194 }
195 InitMouseButtonMap();
196 return true;
197 }
198
199 void EventExecutorLinux::Core::InjectClipboardEvent(
200 const ClipboardEvent& event) {
201 if (!task_runner_->BelongsToCurrentThread()) {
202 task_runner_->PostTask(
203 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
204 return;
205 }
206
207 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
208 clipboard_->InjectClipboardEvent(event);
209 }
210
211 void EventExecutorLinux::Core::InjectKeyEvent(const KeyEvent& event) {
212 // HostEventDispatcher should filter events missing the pressed field.
213 if (!event.has_pressed() || !event.has_usb_keycode())
214 return;
215
216 if (!task_runner_->BelongsToCurrentThread()) {
217 task_runner_->PostTask(FROM_HERE,
218 base::Bind(&Core::InjectKeyEvent, this, event));
219 return;
220 }
221
222 int keycode = UsbKeycodeToNativeKeycode(event.usb_keycode());
223
224 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
225 << " to keycode: " << keycode << std::dec;
226
227 // Ignore events which can't be mapped.
228 if (keycode == InvalidNativeKeycode())
229 return;
230
231 if (event.pressed()) {
232 if (pressed_keys_.find(keycode) != pressed_keys_.end()) {
233 // Key is already held down, so lift the key up to ensure this repeated
234 // press takes effect.
235 XTestFakeKeyEvent(display_, keycode, False, CurrentTime);
236 }
237
238 if (pressed_keys_.empty()) {
239 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat
240 // if network congestion delays the key-up event from the client.
241 saved_auto_repeat_enabled_ = IsAutoRepeatEnabled();
242 if (saved_auto_repeat_enabled_)
243 SetAutoRepeatEnabled(false);
244 }
245 pressed_keys_.insert(keycode);
246 } else {
247 pressed_keys_.erase(keycode);
248 if (pressed_keys_.empty()) {
249 // Re-enable auto-repeat, if necessary, when all keys are released.
250 if (saved_auto_repeat_enabled_)
251 SetAutoRepeatEnabled(true);
252 }
253 }
254
255 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime);
256 XFlush(display_);
257 }
258
259 EventExecutorLinux::Core::~Core() {
260 CHECK(pressed_keys_.empty());
261 }
262
263 void EventExecutorLinux::Core::InitClipboard() {
264 DCHECK(task_runner_->BelongsToCurrentThread());
265 clipboard_ = Clipboard::Create();
266 }
267
268 bool EventExecutorLinux::Core::IsAutoRepeatEnabled() {
269 XKeyboardState state;
270 if (!XGetKeyboardControl(display_, &state)) {
271 LOG(ERROR) << "Failed to get keyboard auto-repeat status, assuming ON.";
272 return true;
273 }
274 return state.global_auto_repeat == AutoRepeatModeOn;
275 }
276
277 void EventExecutorLinux::Core::SetAutoRepeatEnabled(bool mode) {
278 XKeyboardControl control;
279 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
280 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
281 }
282
283 void EventExecutorLinux::Core::InjectScrollWheelClicks(int button, int count) {
284 if (button < 0) {
285 LOG(WARNING) << "Ignoring unmapped scroll wheel button";
286 return;
287 }
288 for (int i = 0; i < count; i++) {
289 // Generate a button-down and a button-up to simulate a wheel click.
290 XTestFakeButtonEvent(display_, button, true, CurrentTime);
291 XTestFakeButtonEvent(display_, button, false, CurrentTime);
292 }
293 }
294
295 void EventExecutorLinux::Core::InjectMouseEvent(const MouseEvent& event) {
296 if (!task_runner_->BelongsToCurrentThread()) {
297 task_runner_->PostTask(FROM_HERE,
298 base::Bind(&Core::InjectMouseEvent, this, event));
299 return;
300 }
301
302 if (event.has_x() && event.has_y()) {
303 // Injecting a motion event immediately before a button release results in
304 // a MotionNotify even if the mouse position hasn't changed, which confuses
305 // apps which assume MotionNotify implies movement. See crbug.com/138075.
306 bool inject_motion = true;
307 SkIPoint new_mouse_position(SkIPoint::Make(event.x(), event.y()));
308 if (event.has_button() && event.has_button_down() && !event.button_down()) {
309 if (new_mouse_position == latest_mouse_position_)
310 inject_motion = false;
311 }
312
313 if (inject_motion) {
314 latest_mouse_position_ =
315 SkIPoint::Make(std::max(0, new_mouse_position.x()),
316 std::max(0, new_mouse_position.y()));
317
318 VLOG(3) << "Moving mouse to " << latest_mouse_position_.x()
319 << "," << latest_mouse_position_.y();
320 XTestFakeMotionEvent(display_, DefaultScreen(display_),
321 latest_mouse_position_.x(),
322 latest_mouse_position_.y(),
323 CurrentTime);
324 }
325 }
326
327 if (event.has_button() && event.has_button_down()) {
328 int button_number = MouseButtonToX11ButtonNumber(event.button());
329
330 if (button_number < 0) {
331 LOG(WARNING) << "Ignoring unknown button type: " << event.button();
332 return;
333 }
334
335 VLOG(3) << "Button " << event.button()
336 << " received, sending "
337 << (event.button_down() ? "down " : "up ")
338 << button_number;
339 XTestFakeButtonEvent(display_, button_number, event.button_down(),
340 CurrentTime);
341 }
342
343 int ticks_y = 0;
344 if (event.has_wheel_delta_y()) {
345 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel;
346 ticks_y = static_cast<int>(wheel_ticks_y_);
347 wheel_ticks_y_ -= ticks_y;
348 }
349 if (ticks_y != 0) {
350 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
351 abs(ticks_y));
352 }
353
354 int ticks_x = 0;
355 if (event.has_wheel_delta_x()) {
356 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel;
357 ticks_x = static_cast<int>(wheel_ticks_x_);
358 wheel_ticks_x_ -= ticks_x;
359 }
360 if (ticks_x != 0) {
361 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
362 abs(ticks_x));
363 }
364
365 XFlush(display_);
366 }
367
368 void EventExecutorLinux::Core::InitMouseButtonMap() {
369 // TODO(rmsousa): Run this on global/device mapping change events.
370
371 // Do not touch global pointer mapping, since this may affect the local user.
372 // Instead, try to work around it by reversing the mapping.
373 // Note that if a user has a global mapping that completely disables a button
374 // (by assigning 0 to it), we won't be able to inject it.
375 int num_buttons = XGetPointerMapping(display_, NULL, 0);
376 scoped_array<unsigned char> pointer_mapping(new unsigned char[num_buttons]);
377 num_buttons = XGetPointerMapping(display_, pointer_mapping.get(),
378 num_buttons);
379 for (int i = 0; i < kNumPointerButtons; i++) {
380 pointer_button_map_[i] = -1;
381 }
382 for (int i = 0; i < num_buttons; i++) {
383 // Reverse the mapping.
384 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons)
385 pointer_button_map_[pointer_mapping[i] - 1] = i + 1;
386 }
387 for (int i = 0; i < kNumPointerButtons; i++) {
388 if (pointer_button_map_[i] == -1)
389 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1;
390 }
391
392 int opcode, event, error;
393 if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) {
394 // If XInput is not available, we're done. But it would be very unusual to
395 // have a server that supports XTest but not XInput, so log it as an error.
396 LOG(ERROR) << "X Input extension not available: " << error;
397 return;
398 }
399
400 // Make sure the XTEST XInput pointer device mapping is trivial. It should be
401 // safe to reset this mapping, as it won't affect the user's local devices.
402 // In fact, the reason why we do this is because an old gnome-settings-daemon
403 // may have mistakenly applied left-handed preferences to the XTEST device.
404 XID device_id = 0;
405 bool device_found = false;
406 int num_devices;
407 XDeviceInfo* devices;
408 devices = XListInputDevices(display_, &num_devices);
409 for (int i = 0; i < num_devices; i++) {
410 XDeviceInfo* device_info = &devices[i];
411 if (device_info->use == IsXExtensionPointer &&
412 strcmp(device_info->name, "Virtual core XTEST pointer") == 0) {
413 device_id = device_info->id;
414 device_found = true;
415 break;
416 }
417 }
418 XFreeDeviceList(devices);
419
420 if (!device_found) {
421 LOG(INFO) << "Cannot find XTest device.";
422 return;
423 }
424
425 XDevice* device = XOpenDevice(display_, device_id);
426 if (!device) {
427 LOG(ERROR) << "Cannot open XTest device.";
428 return;
429 }
430
431 int num_device_buttons = XGetDeviceButtonMapping(display_, device, NULL, 0);
432 scoped_array<unsigned char> button_mapping(new unsigned char[num_buttons]);
433 for (int i = 0; i < num_device_buttons; i++) {
434 button_mapping[i] = i + 1;
435 }
436 error = XSetDeviceButtonMapping(display_, device, button_mapping.get(),
437 num_device_buttons);
438 if (error != Success)
439 LOG(ERROR) << "Failed to set XTest device button mapping: " << error;
440
441 XCloseDevice(display_, device);
442 }
443
444 int EventExecutorLinux::Core::MouseButtonToX11ButtonNumber(
445 MouseEvent::MouseButton button) {
446 switch (button) {
447 case MouseEvent::BUTTON_LEFT:
448 return pointer_button_map_[0];
449
450 case MouseEvent::BUTTON_RIGHT:
451 return pointer_button_map_[2];
452
453 case MouseEvent::BUTTON_MIDDLE:
454 return pointer_button_map_[1];
455
456 case MouseEvent::BUTTON_UNDEFINED:
457 default:
458 return -1;
459 }
460 }
461
462 int EventExecutorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
463 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
464 }
465
466 int EventExecutorLinux::Core::VerticalScrollWheelToX11ButtonNumber(int dy) {
467 // Positive y-values are wheel scroll-up events (button 4), negative y-values
468 // are wheel scroll-down events (button 5).
469 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]);
470 }
471
472 void EventExecutorLinux::Core::Start(
473 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
474 if (!task_runner_->BelongsToCurrentThread()) {
475 task_runner_->PostTask(
476 FROM_HERE,
477 base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
478 return;
479 }
480
481 InitMouseButtonMap();
482
483 clipboard_->Start(client_clipboard.Pass());
484 }
485
486 void EventExecutorLinux::Core::Stop() {
487 if (!task_runner_->BelongsToCurrentThread()) {
488 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
489 return;
490 }
491
492 clipboard_->Stop();
493 }
494
495 } // namespace
496
497 scoped_ptr<EventExecutor> EventExecutor::Create(
498 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
499 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
500 scoped_ptr<EventExecutorLinux> executor(
501 new EventExecutorLinux(main_task_runner));
502 if (!executor->Init())
503 return scoped_ptr<EventExecutor>(NULL);
504 return executor.PassAs<EventExecutor>();
505 }
506
507 } // namespace remoting
OLDNEW
« no previous file with comments | « remoting/host/event_executor.h ('k') | remoting/host/event_executor_mac.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698