Home | History | Annotate | Download | only in host
      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 
     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 third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp .
     38 const float kWheelTicksPerPixel = 3.0f / 160.0f;
     39 
     40 // A class to generate events on Linux.
     41 class InputInjectorLinux : public InputInjector {
     42  public:
     43   explicit InputInjectorLinux(
     44       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
     45   virtual ~InputInjectorLinux();
     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   // InputInjector interface.
     57   virtual void Start(
     58       scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
     59 
     60  private:
     61   // The actual implementation resides in InputInjectorLinux::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 InputInjector 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(InputInjectorLinux);
    133 };
    134 
    135 InputInjectorLinux::InputInjectorLinux(
    136     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
    137   core_ = new Core(task_runner);
    138 }
    139 InputInjectorLinux::~InputInjectorLinux() {
    140   core_->Stop();
    141 }
    142 
    143 bool InputInjectorLinux::Init() {
    144   return core_->Init();
    145 }
    146 
    147 void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) {
    148   core_->InjectClipboardEvent(event);
    149 }
    150 
    151 void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) {
    152   core_->InjectKeyEvent(event);
    153 }
    154 
    155 void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) {
    156   core_->InjectMouseEvent(event);
    157 }
    158 
    159 void InputInjectorLinux::Start(
    160     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    161   core_->Start(client_clipboard.Pass());
    162 }
    163 
    164 InputInjectorLinux::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 InputInjectorLinux::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 InputInjectorLinux::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 InputInjectorLinux::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 InputInjectorLinux::Core::~Core() {
    260   CHECK(pressed_keys_.empty());
    261 }
    262 
    263 void InputInjectorLinux::Core::InitClipboard() {
    264   DCHECK(task_runner_->BelongsToCurrentThread());
    265   clipboard_ = Clipboard::Create();
    266 }
    267 
    268 bool InputInjectorLinux::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 InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) {
    278   XKeyboardControl control;
    279   control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
    280   XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
    281 }
    282 
    283 void InputInjectorLinux::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 InputInjectorLinux::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   // Older client plugins always send scroll events in pixels, which
    344   // must be accumulated host-side. Recent client plugins send both
    345   // pixels and ticks with every scroll event, allowing the host to
    346   // choose the best model on a per-platform basis. Since we can only
    347   // inject ticks on Linux, use them if available.
    348   int ticks_y = 0;
    349   if (event.has_wheel_ticks_y()) {
    350     ticks_y = event.wheel_ticks_y();
    351   } else if (event.has_wheel_delta_y()) {
    352     wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel;
    353     ticks_y = static_cast<int>(wheel_ticks_y_);
    354     wheel_ticks_y_ -= ticks_y;
    355   }
    356   if (ticks_y != 0) {
    357     InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
    358                             abs(ticks_y));
    359   }
    360 
    361   int ticks_x = 0;
    362   if (event.has_wheel_ticks_x()) {
    363     ticks_x = event.wheel_ticks_x();
    364   } else if (event.has_wheel_delta_x()) {
    365     wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel;
    366     ticks_x = static_cast<int>(wheel_ticks_x_);
    367     wheel_ticks_x_ -= ticks_x;
    368   }
    369   if (ticks_x != 0) {
    370     InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
    371                             abs(ticks_x));
    372   }
    373 
    374   XFlush(display_);
    375 }
    376 
    377 void InputInjectorLinux::Core::InitMouseButtonMap() {
    378   // TODO(rmsousa): Run this on global/device mapping change events.
    379 
    380   // Do not touch global pointer mapping, since this may affect the local user.
    381   // Instead, try to work around it by reversing the mapping.
    382   // Note that if a user has a global mapping that completely disables a button
    383   // (by assigning 0 to it), we won't be able to inject it.
    384   int num_buttons = XGetPointerMapping(display_, NULL, 0);
    385   scoped_ptr<unsigned char[]> pointer_mapping(new unsigned char[num_buttons]);
    386   num_buttons = XGetPointerMapping(display_, pointer_mapping.get(),
    387                                    num_buttons);
    388   for (int i = 0; i < kNumPointerButtons; i++) {
    389     pointer_button_map_[i] = -1;
    390   }
    391   for (int i = 0; i < num_buttons; i++) {
    392     // Reverse the mapping.
    393     if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons)
    394       pointer_button_map_[pointer_mapping[i] - 1] = i + 1;
    395   }
    396   for (int i = 0; i < kNumPointerButtons; i++) {
    397     if (pointer_button_map_[i] == -1)
    398       LOG(ERROR) << "Global pointer mapping does not support button " << i + 1;
    399   }
    400 
    401   int opcode, event, error;
    402   if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) {
    403     // If XInput is not available, we're done. But it would be very unusual to
    404     // have a server that supports XTest but not XInput, so log it as an error.
    405     LOG(ERROR) << "X Input extension not available: " << error;
    406     return;
    407   }
    408 
    409   // Make sure the XTEST XInput pointer device mapping is trivial. It should be
    410   // safe to reset this mapping, as it won't affect the user's local devices.
    411   // In fact, the reason why we do this is because an old gnome-settings-daemon
    412   // may have mistakenly applied left-handed preferences to the XTEST device.
    413   XID device_id = 0;
    414   bool device_found = false;
    415   int num_devices;
    416   XDeviceInfo* devices;
    417   devices = XListInputDevices(display_, &num_devices);
    418   for (int i = 0; i < num_devices; i++) {
    419     XDeviceInfo* device_info = &devices[i];
    420     if (device_info->use == IsXExtensionPointer &&
    421         strcmp(device_info->name, "Virtual core XTEST pointer") == 0) {
    422       device_id = device_info->id;
    423       device_found = true;
    424       break;
    425     }
    426   }
    427   XFreeDeviceList(devices);
    428 
    429   if (!device_found) {
    430     LOG(INFO) << "Cannot find XTest device.";
    431     return;
    432   }
    433 
    434   XDevice* device = XOpenDevice(display_, device_id);
    435   if (!device) {
    436     LOG(ERROR) << "Cannot open XTest device.";
    437     return;
    438   }
    439 
    440   int num_device_buttons = XGetDeviceButtonMapping(display_, device, NULL, 0);
    441   scoped_ptr<unsigned char[]> button_mapping(new unsigned char[num_buttons]);
    442   for (int i = 0; i < num_device_buttons; i++) {
    443     button_mapping[i] = i + 1;
    444   }
    445   error = XSetDeviceButtonMapping(display_, device, button_mapping.get(),
    446                                   num_device_buttons);
    447   if (error != Success)
    448     LOG(ERROR) << "Failed to set XTest device button mapping: " << error;
    449 
    450   XCloseDevice(display_, device);
    451 }
    452 
    453 int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber(
    454     MouseEvent::MouseButton button) {
    455   switch (button) {
    456     case MouseEvent::BUTTON_LEFT:
    457       return pointer_button_map_[0];
    458 
    459     case MouseEvent::BUTTON_RIGHT:
    460       return pointer_button_map_[2];
    461 
    462     case MouseEvent::BUTTON_MIDDLE:
    463       return pointer_button_map_[1];
    464 
    465     case MouseEvent::BUTTON_UNDEFINED:
    466     default:
    467       return -1;
    468   }
    469 }
    470 
    471 int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
    472   return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
    473 }
    474 
    475 int InputInjectorLinux::Core::VerticalScrollWheelToX11ButtonNumber(int dy) {
    476   // Positive y-values are wheel scroll-up events (button 4), negative y-values
    477   // are wheel scroll-down events (button 5).
    478   return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]);
    479 }
    480 
    481 void InputInjectorLinux::Core::Start(
    482     scoped_ptr<protocol::ClipboardStub> client_clipboard) {
    483   if (!task_runner_->BelongsToCurrentThread()) {
    484     task_runner_->PostTask(
    485         FROM_HERE,
    486         base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
    487     return;
    488   }
    489 
    490   InitMouseButtonMap();
    491 
    492   clipboard_->Start(client_clipboard.Pass());
    493 }
    494 
    495 void InputInjectorLinux::Core::Stop() {
    496   if (!task_runner_->BelongsToCurrentThread()) {
    497     task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
    498     return;
    499   }
    500 
    501   clipboard_->Stop();
    502 }
    503 
    504 }  // namespace
    505 
    506 scoped_ptr<InputInjector> InputInjector::Create(
    507     scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
    508     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
    509   scoped_ptr<InputInjectorLinux> injector(
    510       new InputInjectorLinux(main_task_runner));
    511   if (!injector->Init())
    512     return scoped_ptr<InputInjector>();
    513   return injector.PassAs<InputInjector>();
    514 }
    515 
    516 }  // namespace remoting
    517