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