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