Remove dependency on content from remoting_host.
[chromium-blink-merge.git] / remoting / host / input_injector_x11.cc
blob53e160451eba81490cb9c3139f88f890df510dd6
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.
5 #include "remoting/host/input_injector.h"
7 #include <X11/extensions/XInput.h>
8 #include <X11/extensions/XTest.h>
9 #include <X11/Xlib.h>
10 #include <X11/XKBlib.h>
12 #include <set>
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 #if defined(OS_CHROMEOS)
22 #include "remoting/host/chromeos/point_transformer.h"
23 #endif
24 #include "remoting/host/clipboard.h"
25 #include "remoting/host/linux/unicode_to_keysym.h"
26 #include "remoting/proto/internal.pb.h"
27 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
28 #include "ui/events/keycodes/dom4/keycode_converter.h"
30 namespace remoting {
32 namespace {
34 using protocol::ClipboardEvent;
35 using protocol::KeyEvent;
36 using protocol::TextEvent;
37 using protocol::MouseEvent;
39 bool FindKeycodeForKeySym(Display* display,
40 KeySym key_sym,
41 uint32_t* keycode,
42 uint32_t* modifiers) {
43 *keycode = XKeysymToKeycode(display, key_sym);
45 const uint32_t kModifiersToTry[] = {
47 ShiftMask,
48 Mod2Mask,
49 Mod3Mask,
50 Mod4Mask,
51 ShiftMask | Mod2Mask,
52 ShiftMask | Mod3Mask,
53 ShiftMask | Mod4Mask,
56 // TODO(sergeyu): Is there a better way to find modifiers state?
57 for (size_t i = 0; i < arraysize(kModifiersToTry); ++i) {
58 unsigned long key_sym_with_mods;
59 if (XkbLookupKeySym(display, *keycode, kModifiersToTry[i], nullptr,
60 &key_sym_with_mods) &&
61 key_sym_with_mods == key_sym) {
62 *modifiers = kModifiersToTry[i];
63 return true;
67 return false;
70 // Finds a keycode and set of modifiers that generate character with the
71 // specified |code_point|.
72 bool FindKeycodeForUnicode(Display* display,
73 uint32_t code_point,
74 uint32_t* keycode,
75 uint32_t* modifiers) {
76 std::vector<uint32_t> keysyms;
77 GetKeySymsForUnicode(code_point, &keysyms);
79 for (std::vector<uint32_t>::iterator it = keysyms.begin();
80 it != keysyms.end(); ++it) {
81 if (FindKeycodeForKeySym(display, *it, keycode, modifiers)) {
82 return true;
86 return false;
89 // Pixel-to-wheel-ticks conversion ratio used by GTK.
90 // From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp .
91 const float kWheelTicksPerPixel = 3.0f / 160.0f;
93 // A class to generate events on X11.
94 class InputInjectorX11 : public InputInjector {
95 public:
96 explicit InputInjectorX11(
97 scoped_refptr<base::SingleThreadTaskRunner> task_runner);
98 ~InputInjectorX11() override;
100 bool Init();
102 // Clipboard stub interface.
103 void InjectClipboardEvent(const ClipboardEvent& event) override;
105 // InputStub interface.
106 void InjectKeyEvent(const KeyEvent& event) override;
107 void InjectTextEvent(const TextEvent& event) override;
108 void InjectMouseEvent(const MouseEvent& event) override;
110 // InputInjector interface.
111 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) override;
113 private:
114 // The actual implementation resides in InputInjectorX11::Core class.
115 class Core : public base::RefCountedThreadSafe<Core> {
116 public:
117 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
119 bool Init();
121 // Mirrors the ClipboardStub interface.
122 void InjectClipboardEvent(const ClipboardEvent& event);
124 // Mirrors the InputStub interface.
125 void InjectKeyEvent(const KeyEvent& event);
126 void InjectTextEvent(const TextEvent& event);
127 void InjectMouseEvent(const MouseEvent& event);
129 // Mirrors the InputInjector interface.
130 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
132 void Stop();
134 private:
135 friend class base::RefCountedThreadSafe<Core>;
136 virtual ~Core();
138 void InitClipboard();
140 // Queries whether keyboard auto-repeat is globally enabled. This is used
141 // to decide whether to temporarily disable then restore this setting. If
142 // auto-repeat has already been disabled, this class should leave it
143 // untouched.
144 bool IsAutoRepeatEnabled();
146 // Enables or disables keyboard auto-repeat globally.
147 void SetAutoRepeatEnabled(bool enabled);
149 void InjectScrollWheelClicks(int button, int count);
150 // Compensates for global button mappings and resets the XTest device
151 // mapping.
152 void InitMouseButtonMap();
153 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button);
154 int HorizontalScrollWheelToX11ButtonNumber(int dx);
155 int VerticalScrollWheelToX11ButtonNumber(int dy);
157 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
159 std::set<int> pressed_keys_;
160 webrtc::DesktopVector latest_mouse_position_;
161 float wheel_ticks_x_;
162 float wheel_ticks_y_;
164 // X11 graphics context.
165 Display* display_;
166 Window root_window_;
168 int test_event_base_;
169 int test_error_base_;
171 // Number of buttons we support.
172 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right.
173 static const int kNumPointerButtons = 7;
175 int pointer_button_map_[kNumPointerButtons];
177 #if defined(OS_CHROMEOS)
178 PointTransformer point_transformer_;
179 #endif
181 scoped_ptr<Clipboard> clipboard_;
183 bool saved_auto_repeat_enabled_;
185 DISALLOW_COPY_AND_ASSIGN(Core);
188 scoped_refptr<Core> core_;
190 DISALLOW_COPY_AND_ASSIGN(InputInjectorX11);
193 InputInjectorX11::InputInjectorX11(
194 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
195 core_ = new Core(task_runner);
198 InputInjectorX11::~InputInjectorX11() {
199 core_->Stop();
202 bool InputInjectorX11::Init() {
203 return core_->Init();
206 void InputInjectorX11::InjectClipboardEvent(const ClipboardEvent& event) {
207 core_->InjectClipboardEvent(event);
210 void InputInjectorX11::InjectKeyEvent(const KeyEvent& event) {
211 core_->InjectKeyEvent(event);
214 void InputInjectorX11::InjectTextEvent(const TextEvent& event) {
215 core_->InjectTextEvent(event);
218 void InputInjectorX11::InjectMouseEvent(const MouseEvent& event) {
219 core_->InjectMouseEvent(event);
222 void InputInjectorX11::Start(
223 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
224 core_->Start(client_clipboard.Pass());
227 InputInjectorX11::Core::Core(
228 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
229 : task_runner_(task_runner),
230 latest_mouse_position_(-1, -1),
231 wheel_ticks_x_(0.0f),
232 wheel_ticks_y_(0.0f),
233 display_(XOpenDisplay(nullptr)),
234 root_window_(BadValue),
235 saved_auto_repeat_enabled_(false) {
238 bool InputInjectorX11::Core::Init() {
239 CHECK(display_);
241 if (!task_runner_->BelongsToCurrentThread())
242 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this));
244 root_window_ = RootWindow(display_, DefaultScreen(display_));
245 if (root_window_ == BadValue) {
246 LOG(ERROR) << "Unable to get the root window";
247 return false;
250 // TODO(ajwong): Do we want to check the major/minor version at all for XTest?
251 int major = 0;
252 int minor = 0;
253 if (!XTestQueryExtension(display_, &test_event_base_, &test_error_base_,
254 &major, &minor)) {
255 LOG(ERROR) << "Server does not support XTest.";
256 return false;
258 InitMouseButtonMap();
259 return true;
262 void InputInjectorX11::Core::InjectClipboardEvent(
263 const ClipboardEvent& event) {
264 if (!task_runner_->BelongsToCurrentThread()) {
265 task_runner_->PostTask(
266 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event));
267 return;
270 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
271 clipboard_->InjectClipboardEvent(event);
274 void InputInjectorX11::Core::InjectKeyEvent(const KeyEvent& event) {
275 // HostEventDispatcher should filter events missing the pressed field.
276 if (!event.has_pressed() || !event.has_usb_keycode())
277 return;
279 if (!task_runner_->BelongsToCurrentThread()) {
280 task_runner_->PostTask(FROM_HERE,
281 base::Bind(&Core::InjectKeyEvent, this, event));
282 return;
285 int keycode =
286 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode());
288 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
289 << " to keycode: " << keycode << std::dec;
291 // Ignore events which can't be mapped.
292 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode())
293 return;
295 if (event.pressed()) {
296 if (pressed_keys_.find(keycode) != pressed_keys_.end()) {
297 // Key is already held down, so lift the key up to ensure this repeated
298 // press takes effect.
299 XTestFakeKeyEvent(display_, keycode, False, CurrentTime);
302 if (pressed_keys_.empty()) {
303 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat
304 // if network congestion delays the key-up event from the client.
305 saved_auto_repeat_enabled_ = IsAutoRepeatEnabled();
306 if (saved_auto_repeat_enabled_)
307 SetAutoRepeatEnabled(false);
309 pressed_keys_.insert(keycode);
310 } else {
311 pressed_keys_.erase(keycode);
312 if (pressed_keys_.empty()) {
313 // Re-enable auto-repeat, if necessary, when all keys are released.
314 if (saved_auto_repeat_enabled_)
315 SetAutoRepeatEnabled(true);
319 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime);
320 XFlush(display_);
323 void InputInjectorX11::Core::InjectTextEvent(const TextEvent& event) {
324 if (!task_runner_->BelongsToCurrentThread()) {
325 task_runner_->PostTask(FROM_HERE,
326 base::Bind(&Core::InjectTextEvent, this, event));
327 return;
330 // Release all keys before injecting text event. This is necessary to avoid
331 // any interference with the currently pressed keys. E.g. if Shift is pressed
332 // when TextEvent is received.
333 for (int key : pressed_keys_) {
334 XTestFakeKeyEvent(display_, key, False, CurrentTime);
336 pressed_keys_.clear();
338 const std::string text = event.text();
339 for (int32 index = 0; index < static_cast<int32>(text.size()); ++index) {
340 uint32_t code_point;
341 if (!base::ReadUnicodeCharacter(
342 text.c_str(), text.size(), &index, &code_point)) {
343 continue;
346 uint32_t keycode;
347 uint32_t modifiers;
348 if (!FindKeycodeForUnicode(display_, code_point, &keycode, &modifiers))
349 continue;
351 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, modifiers);
353 XTestFakeKeyEvent(display_, keycode, True, CurrentTime);
354 XTestFakeKeyEvent(display_, keycode, False, CurrentTime);
356 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, 0);
359 XFlush(display_);
362 InputInjectorX11::Core::~Core() {
363 CHECK(pressed_keys_.empty());
366 void InputInjectorX11::Core::InitClipboard() {
367 DCHECK(task_runner_->BelongsToCurrentThread());
368 clipboard_ = Clipboard::Create();
371 bool InputInjectorX11::Core::IsAutoRepeatEnabled() {
372 XKeyboardState state;
373 if (!XGetKeyboardControl(display_, &state)) {
374 LOG(ERROR) << "Failed to get keyboard auto-repeat status, assuming ON.";
375 return true;
377 return state.global_auto_repeat == AutoRepeatModeOn;
380 void InputInjectorX11::Core::SetAutoRepeatEnabled(bool mode) {
381 XKeyboardControl control;
382 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
383 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
386 void InputInjectorX11::Core::InjectScrollWheelClicks(int button, int count) {
387 if (button < 0) {
388 LOG(WARNING) << "Ignoring unmapped scroll wheel button";
389 return;
391 for (int i = 0; i < count; i++) {
392 // Generate a button-down and a button-up to simulate a wheel click.
393 XTestFakeButtonEvent(display_, button, true, CurrentTime);
394 XTestFakeButtonEvent(display_, button, false, CurrentTime);
398 void InputInjectorX11::Core::InjectMouseEvent(const MouseEvent& event) {
399 if (!task_runner_->BelongsToCurrentThread()) {
400 task_runner_->PostTask(FROM_HERE,
401 base::Bind(&Core::InjectMouseEvent, this, event));
402 return;
405 if (event.has_delta_x() &&
406 event.has_delta_y() &&
407 (event.delta_x() != 0 || event.delta_y() != 0)) {
408 latest_mouse_position_.set(-1, -1);
409 VLOG(3) << "Moving mouse by " << event.delta_x() << "," << event.delta_y();
410 XTestFakeRelativeMotionEvent(display_,
411 event.delta_x(), event.delta_y(),
412 CurrentTime);
414 } else if (event.has_x() && event.has_y()) {
415 // Injecting a motion event immediately before a button release results in
416 // a MotionNotify even if the mouse position hasn't changed, which confuses
417 // apps which assume MotionNotify implies movement. See crbug.com/138075.
418 bool inject_motion = true;
419 webrtc::DesktopVector new_mouse_position(event.x(), event.y());
420 #if defined(OS_CHROMEOS)
421 // Interim hack to handle display rotation on Chrome OS.
422 // TODO(kelvin): Remove this when Chrome OS has completely migrated to
423 // Ozone (crbug.com/439287).
424 gfx::PointF screen_location = point_transformer_.ToScreenCoordinates(
425 gfx::PointF(event.x(), event.y()));
426 new_mouse_position.set(screen_location.x(), screen_location.y());
427 #endif
428 if (event.has_button() && event.has_button_down() && !event.button_down()) {
429 if (new_mouse_position.equals(latest_mouse_position_))
430 inject_motion = false;
433 if (inject_motion) {
434 latest_mouse_position_.set(std::max(0, new_mouse_position.x()),
435 std::max(0, new_mouse_position.y()));
437 VLOG(3) << "Moving mouse to " << latest_mouse_position_.x()
438 << "," << latest_mouse_position_.y();
439 XTestFakeMotionEvent(display_, DefaultScreen(display_),
440 latest_mouse_position_.x(),
441 latest_mouse_position_.y(),
442 CurrentTime);
446 if (event.has_button() && event.has_button_down()) {
447 int button_number = MouseButtonToX11ButtonNumber(event.button());
449 if (button_number < 0) {
450 LOG(WARNING) << "Ignoring unknown button type: " << event.button();
451 return;
454 VLOG(3) << "Button " << event.button()
455 << " received, sending "
456 << (event.button_down() ? "down " : "up ")
457 << button_number;
458 XTestFakeButtonEvent(display_, button_number, event.button_down(),
459 CurrentTime);
462 // Older client plugins always send scroll events in pixels, which
463 // must be accumulated host-side. Recent client plugins send both
464 // pixels and ticks with every scroll event, allowing the host to
465 // choose the best model on a per-platform basis. Since we can only
466 // inject ticks on Linux, use them if available.
467 int ticks_y = 0;
468 if (event.has_wheel_ticks_y()) {
469 ticks_y = event.wheel_ticks_y();
470 } else if (event.has_wheel_delta_y()) {
471 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel;
472 ticks_y = static_cast<int>(wheel_ticks_y_);
473 wheel_ticks_y_ -= ticks_y;
475 if (ticks_y != 0) {
476 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
477 abs(ticks_y));
480 int ticks_x = 0;
481 if (event.has_wheel_ticks_x()) {
482 ticks_x = event.wheel_ticks_x();
483 } else if (event.has_wheel_delta_x()) {
484 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel;
485 ticks_x = static_cast<int>(wheel_ticks_x_);
486 wheel_ticks_x_ -= ticks_x;
488 if (ticks_x != 0) {
489 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
490 abs(ticks_x));
493 XFlush(display_);
496 void InputInjectorX11::Core::InitMouseButtonMap() {
497 // TODO(rmsousa): Run this on global/device mapping change events.
499 // Do not touch global pointer mapping, since this may affect the local user.
500 // Instead, try to work around it by reversing the mapping.
501 // Note that if a user has a global mapping that completely disables a button
502 // (by assigning 0 to it), we won't be able to inject it.
503 int num_buttons = XGetPointerMapping(display_, nullptr, 0);
504 scoped_ptr<unsigned char[]> pointer_mapping(new unsigned char[num_buttons]);
505 num_buttons = XGetPointerMapping(display_, pointer_mapping.get(),
506 num_buttons);
507 for (int i = 0; i < kNumPointerButtons; i++) {
508 pointer_button_map_[i] = -1;
510 for (int i = 0; i < num_buttons; i++) {
511 // Reverse the mapping.
512 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons)
513 pointer_button_map_[pointer_mapping[i] - 1] = i + 1;
515 for (int i = 0; i < kNumPointerButtons; i++) {
516 if (pointer_button_map_[i] == -1)
517 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1;
520 int opcode, event, error;
521 if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) {
522 // If XInput is not available, we're done. But it would be very unusual to
523 // have a server that supports XTest but not XInput, so log it as an error.
524 LOG(ERROR) << "X Input extension not available: " << error;
525 return;
528 // Make sure the XTEST XInput pointer device mapping is trivial. It should be
529 // safe to reset this mapping, as it won't affect the user's local devices.
530 // In fact, the reason why we do this is because an old gnome-settings-daemon
531 // may have mistakenly applied left-handed preferences to the XTEST device.
532 XID device_id = 0;
533 bool device_found = false;
534 int num_devices;
535 XDeviceInfo* devices;
536 devices = XListInputDevices(display_, &num_devices);
537 for (int i = 0; i < num_devices; i++) {
538 XDeviceInfo* device_info = &devices[i];
539 if (device_info->use == IsXExtensionPointer &&
540 strcmp(device_info->name, "Virtual core XTEST pointer") == 0) {
541 device_id = device_info->id;
542 device_found = true;
543 break;
546 XFreeDeviceList(devices);
548 if (!device_found) {
549 HOST_LOG << "Cannot find XTest device.";
550 return;
553 XDevice* device = XOpenDevice(display_, device_id);
554 if (!device) {
555 LOG(ERROR) << "Cannot open XTest device.";
556 return;
559 int num_device_buttons =
560 XGetDeviceButtonMapping(display_, device, nullptr, 0);
561 scoped_ptr<unsigned char[]> button_mapping(new unsigned char[num_buttons]);
562 for (int i = 0; i < num_device_buttons; i++) {
563 button_mapping[i] = i + 1;
565 error = XSetDeviceButtonMapping(display_, device, button_mapping.get(),
566 num_device_buttons);
567 if (error != Success)
568 LOG(ERROR) << "Failed to set XTest device button mapping: " << error;
570 XCloseDevice(display_, device);
573 int InputInjectorX11::Core::MouseButtonToX11ButtonNumber(
574 MouseEvent::MouseButton button) {
575 switch (button) {
576 case MouseEvent::BUTTON_LEFT:
577 return pointer_button_map_[0];
579 case MouseEvent::BUTTON_RIGHT:
580 return pointer_button_map_[2];
582 case MouseEvent::BUTTON_MIDDLE:
583 return pointer_button_map_[1];
585 case MouseEvent::BUTTON_UNDEFINED:
586 default:
587 return -1;
591 int InputInjectorX11::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
592 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
595 int InputInjectorX11::Core::VerticalScrollWheelToX11ButtonNumber(int dy) {
596 // Positive y-values are wheel scroll-up events (button 4), negative y-values
597 // are wheel scroll-down events (button 5).
598 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]);
601 void InputInjectorX11::Core::Start(
602 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
603 if (!task_runner_->BelongsToCurrentThread()) {
604 task_runner_->PostTask(
605 FROM_HERE,
606 base::Bind(&Core::Start, this, base::Passed(&client_clipboard)));
607 return;
610 InitMouseButtonMap();
612 clipboard_->Start(client_clipboard.Pass());
615 void InputInjectorX11::Core::Stop() {
616 if (!task_runner_->BelongsToCurrentThread()) {
617 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
618 return;
621 clipboard_->Stop();
624 } // namespace
626 scoped_ptr<InputInjector> InputInjector::Create(
627 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
628 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
629 scoped_ptr<InputInjectorX11> injector(
630 new InputInjectorX11(main_task_runner));
631 if (!injector->Init())
632 return nullptr;
633 return injector.Pass();
636 } // namespace remoting