Battery Status API: add UMA logging for Linux.
[chromium-blink-merge.git] / remoting / host / input_injector_linux.cc
blob34e1d38e856d106d8082571b711d6f02c4c67f53
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 #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"
27 namespace remoting {
29 namespace {
31 using protocol::ClipboardEvent;
32 using protocol::KeyEvent;
33 using protocol::TextEvent;
34 using protocol::MouseEvent;
36 bool FindKeycodeForKeySym(Display* display,
37 KeySym key_sym,
38 uint32_t* keycode,
39 uint32_t* modifiers) {
40 *keycode = XKeysymToKeycode(display, key_sym);
42 const uint32_t kModifiersToTry[] = {
44 ShiftMask,
45 Mod2Mask,
46 Mod3Mask,
47 Mod4Mask,
48 ShiftMask | Mod2Mask,
49 ShiftMask | Mod3Mask,
50 ShiftMask | Mod4Mask,
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;
64 return false;
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);
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;
83 return false;
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;
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();
97 bool Init();
99 // Clipboard stub interface.
100 virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
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;
107 // InputInjector interface.
108 virtual void Start(
109 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
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);
117 bool Init();
119 // Mirrors the ClipboardStub interface.
120 void InjectClipboardEvent(const ClipboardEvent& event);
122 // Mirrors the InputStub interface.
123 void InjectKeyEvent(const KeyEvent& event);
124 void InjectTextEvent(const TextEvent& event);
125 void InjectMouseEvent(const MouseEvent& event);
127 // Mirrors the InputInjector interface.
128 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
130 void Stop();
132 private:
133 friend class base::RefCountedThreadSafe<Core>;
134 virtual ~Core();
136 void InitClipboard();
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();
144 // Enables or disables keyboard auto-repeat globally.
145 void SetAutoRepeatEnabled(bool enabled);
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);
155 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
157 std::set<int> pressed_keys_;
158 webrtc::DesktopVector latest_mouse_position_;
159 float wheel_ticks_x_;
160 float wheel_ticks_y_;
162 // X11 graphics context.
163 Display* display_;
164 Window root_window_;
166 int test_event_base_;
167 int test_error_base_;
169 // Number of buttons we support.
170 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right.
171 static const int kNumPointerButtons = 7;
173 int pointer_button_map_[kNumPointerButtons];
175 scoped_ptr<Clipboard> clipboard_;
177 bool saved_auto_repeat_enabled_;
179 DISALLOW_COPY_AND_ASSIGN(Core);
182 scoped_refptr<Core> core_;
184 DISALLOW_COPY_AND_ASSIGN(InputInjectorLinux);
187 InputInjectorLinux::InputInjectorLinux(
188 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
189 core_ = new Core(task_runner);
192 InputInjectorLinux::~InputInjectorLinux() {
193 core_->Stop();
196 bool InputInjectorLinux::Init() {
197 return core_->Init();
200 void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) {
201 core_->InjectClipboardEvent(event);
204 void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) {
205 core_->InjectKeyEvent(event);
208 void InputInjectorLinux::InjectTextEvent(const TextEvent& event) {
209 core_->InjectTextEvent(event);
212 void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) {
213 core_->InjectMouseEvent(event);
216 void InputInjectorLinux::Start(
217 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
218 core_->Start(client_clipboard.Pass());
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) {
232 bool InputInjectorLinux::Core::Init() {
233 CHECK(display_);
235 if (!task_runner_->BelongsToCurrentThread())
236 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this));
238 root_window_ = RootWindow(display_, DefaultScreen(display_));
239 if (root_window_ == BadValue) {
240 LOG(ERROR) << "Unable to get the root window";
241 return false;
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;
252 InitMouseButtonMap();
253 return true;
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;
264 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
265 clipboard_->InjectClipboardEvent(event);
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;
273 if (!task_runner_->BelongsToCurrentThread()) {
274 task_runner_->PostTask(FROM_HERE,
275 base::Bind(&Core::InjectKeyEvent, this, event));
276 return;
279 int keycode =
280 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode());
282 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
283 << " to keycode: " << keycode << std::dec;
285 // Ignore events which can't be mapped.
286 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode())
287 return;
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);
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);
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);
313 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime);
314 XFlush(display_);
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;
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;
332 uint32_t keycode;
333 uint32_t modifiers;
334 if (!FindKeycodeForUnicode(display_, code_point, &keycode, &modifiers))
335 continue;
337 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, modifiers);
339 XTestFakeKeyEvent(display_, keycode, True, CurrentTime);
340 XTestFakeKeyEvent(display_, keycode, False, CurrentTime);
342 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, 0);
345 XFlush(display_);
348 InputInjectorLinux::Core::~Core() {
349 CHECK(pressed_keys_.empty());
352 void InputInjectorLinux::Core::InitClipboard() {
353 DCHECK(task_runner_->BelongsToCurrentThread());
354 clipboard_ = Clipboard::Create();
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;
363 return state.global_auto_repeat == AutoRepeatModeOn;
366 void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) {
367 XKeyboardControl control;
368 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
369 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
372 void InputInjectorLinux::Core::InjectScrollWheelClicks(int button, int count) {
373 if (button < 0) {
374 LOG(WARNING) << "Ignoring unmapped scroll wheel button";
375 return;
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);
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;
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);
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;
412 if (inject_motion) {
413 latest_mouse_position_.set(std::max(0, new_mouse_position.x()),
414 std::max(0, new_mouse_position.y()));
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);
425 if (event.has_button() && event.has_button_down()) {
426 int button_number = MouseButtonToX11ButtonNumber(event.button());
428 if (button_number < 0) {
429 LOG(WARNING) << "Ignoring unknown button type: " << event.button();
430 return;
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);
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;
454 if (ticks_y != 0) {
455 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
456 abs(ticks_y));
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;
467 if (ticks_x != 0) {
468 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
469 abs(ticks_x));
472 XFlush(display_);
475 void InputInjectorLinux::Core::InitMouseButtonMap() {
476 // TODO(rmsousa): Run this on global/device mapping change events.
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;
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;
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;
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;
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;
525 XFreeDeviceList(devices);
527 if (!device_found) {
528 HOST_LOG << "Cannot find XTest device.";
529 return;
532 XDevice* device = XOpenDevice(display_, device_id);
533 if (!device) {
534 LOG(ERROR) << "Cannot open XTest device.";
535 return;
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;
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;
548 XCloseDevice(display_, device);
551 int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber(
552 MouseEvent::MouseButton button) {
553 switch (button) {
554 case MouseEvent::BUTTON_LEFT:
555 return pointer_button_map_[0];
557 case MouseEvent::BUTTON_RIGHT:
558 return pointer_button_map_[2];
560 case MouseEvent::BUTTON_MIDDLE:
561 return pointer_button_map_[1];
563 case MouseEvent::BUTTON_UNDEFINED:
564 default:
565 return -1;
569 int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
570 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
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]);
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;
588 InitMouseButtonMap();
590 clipboard_->Start(client_clipboard.Pass());
593 void InputInjectorLinux::Core::Stop() {
594 if (!task_runner_->BelongsToCurrentThread()) {
595 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
596 return;
599 clipboard_->Stop();
602 } // namespace
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>();
614 } // namespace remoting