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