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
27namespace remoting {
28
29namespace {
30
31using protocol::ClipboardEvent;
32using protocol::KeyEvent;
33using protocol::TextEvent;
34using protocol::MouseEvent;
35
36bool 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|.
69bool 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 .
88const float kWheelTicksPerPixel = 3.0f / 160.0f;
89
90// A class to generate events on Linux.
91class 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
187InputInjectorLinux::InputInjectorLinux(
188    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
189  core_ = new Core(task_runner);
190}
191
192InputInjectorLinux::~InputInjectorLinux() {
193  core_->Stop();
194}
195
196bool InputInjectorLinux::Init() {
197  return core_->Init();
198}
199
200void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) {
201  core_->InjectClipboardEvent(event);
202}
203
204void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) {
205  core_->InjectKeyEvent(event);
206}
207
208void InputInjectorLinux::InjectTextEvent(const TextEvent& event) {
209  core_->InjectTextEvent(event);
210}
211
212void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) {
213  core_->InjectMouseEvent(event);
214}
215
216void InputInjectorLinux::Start(
217    scoped_ptr<protocol::ClipboardStub> client_clipboard) {
218  core_->Start(client_clipboard.Pass());
219}
220
221InputInjectorLinux::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
232bool 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
256void 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
268void 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
317void 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
348InputInjectorLinux::Core::~Core() {
349  CHECK(pressed_keys_.empty());
350}
351
352void InputInjectorLinux::Core::InitClipboard() {
353  DCHECK(task_runner_->BelongsToCurrentThread());
354  clipboard_ = Clipboard::Create();
355}
356
357bool 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
366void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) {
367  XKeyboardControl control;
368  control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
369  XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
370}
371
372void 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
384void 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
475void 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
551int 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
569int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
570  return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
571}
572
573int 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
579void 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
593void 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
604scoped_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
615