Remove some NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
[chromium-blink-merge.git] / remoting / host / input_injector_linux.cc
blobc9763b209b894a19ef51fdfbf7b1c7df85cfd1c9
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>
11 #include <set>
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"
24 namespace remoting {
26 namespace {
28 using protocol::ClipboardEvent;
29 using protocol::KeyEvent;
30 using protocol::TextEvent;
31 using protocol::MouseEvent;
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;
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();
44 bool Init();
46 // Clipboard stub interface.
47 virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE;
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;
54 // InputInjector interface.
55 virtual void Start(
56 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE;
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);
64 bool Init();
66 // Mirrors the ClipboardStub interface.
67 void InjectClipboardEvent(const ClipboardEvent& event);
69 // Mirrors the InputStub interface.
70 void InjectKeyEvent(const KeyEvent& event);
71 void InjectTextEvent(const TextEvent& event);
72 void InjectMouseEvent(const MouseEvent& event);
74 // Mirrors the InputInjector interface.
75 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard);
77 void Stop();
79 private:
80 friend class base::RefCountedThreadSafe<Core>;
81 virtual ~Core();
83 void InitClipboard();
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();
91 // Enables or disables keyboard auto-repeat globally.
92 void SetAutoRepeatEnabled(bool enabled);
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);
102 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
104 std::set<int> pressed_keys_;
105 webrtc::DesktopVector latest_mouse_position_;
106 float wheel_ticks_x_;
107 float wheel_ticks_y_;
109 // X11 graphics context.
110 Display* display_;
111 Window root_window_;
113 int test_event_base_;
114 int test_error_base_;
116 // Number of buttons we support.
117 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right.
118 static const int kNumPointerButtons = 7;
120 int pointer_button_map_[kNumPointerButtons];
122 scoped_ptr<Clipboard> clipboard_;
124 bool saved_auto_repeat_enabled_;
126 DISALLOW_COPY_AND_ASSIGN(Core);
129 scoped_refptr<Core> core_;
131 DISALLOW_COPY_AND_ASSIGN(InputInjectorLinux);
134 InputInjectorLinux::InputInjectorLinux(
135 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
136 core_ = new Core(task_runner);
138 InputInjectorLinux::~InputInjectorLinux() {
139 core_->Stop();
142 bool InputInjectorLinux::Init() {
143 return core_->Init();
146 void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) {
147 core_->InjectClipboardEvent(event);
150 void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) {
151 core_->InjectKeyEvent(event);
154 void InputInjectorLinux::InjectTextEvent(const TextEvent& event) {
155 core_->InjectTextEvent(event);
158 void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) {
159 core_->InjectMouseEvent(event);
162 void InputInjectorLinux::Start(
163 scoped_ptr<protocol::ClipboardStub> client_clipboard) {
164 core_->Start(client_clipboard.Pass());
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) {
178 bool InputInjectorLinux::Core::Init() {
179 CHECK(display_);
181 if (!task_runner_->BelongsToCurrentThread())
182 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this));
184 root_window_ = RootWindow(display_, DefaultScreen(display_));
185 if (root_window_ == BadValue) {
186 LOG(ERROR) << "Unable to get the root window";
187 return false;
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;
198 InitMouseButtonMap();
199 return true;
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;
210 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
211 clipboard_->InjectClipboardEvent(event);
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;
219 if (!task_runner_->BelongsToCurrentThread()) {
220 task_runner_->PostTask(FROM_HERE,
221 base::Bind(&Core::InjectKeyEvent, this, event));
222 return;
225 ui::KeycodeConverter* key_converter = ui::KeycodeConverter::GetInstance();
226 int keycode = key_converter->UsbKeycodeToNativeKeycode(event.usb_keycode());
228 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
229 << " to keycode: " << keycode << std::dec;
231 // Ignore events which can't be mapped.
232 if (keycode == key_converter->InvalidNativeKeycode())
233 return;
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);
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);
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);
259 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime);
260 XFlush(display_);
263 void InputInjectorLinux::Core::InjectTextEvent(const TextEvent& event) {
264 NOTIMPLEMENTED();
267 InputInjectorLinux::Core::~Core() {
268 CHECK(pressed_keys_.empty());
271 void InputInjectorLinux::Core::InitClipboard() {
272 DCHECK(task_runner_->BelongsToCurrentThread());
273 clipboard_ = Clipboard::Create();
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;
282 return state.global_auto_repeat == AutoRepeatModeOn;
285 void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) {
286 XKeyboardControl control;
287 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff;
288 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control);
291 void InputInjectorLinux::Core::InjectScrollWheelClicks(int button, int count) {
292 if (button < 0) {
293 LOG(WARNING) << "Ignoring unmapped scroll wheel button";
294 return;
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);
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;
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);
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;
331 if (inject_motion) {
332 latest_mouse_position_.set(std::max(0, new_mouse_position.x()),
333 std::max(0, new_mouse_position.y()));
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);
344 if (event.has_button() && event.has_button_down()) {
345 int button_number = MouseButtonToX11ButtonNumber(event.button());
347 if (button_number < 0) {
348 LOG(WARNING) << "Ignoring unknown button type: " << event.button();
349 return;
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);
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;
373 if (ticks_y != 0) {
374 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
375 abs(ticks_y));
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;
386 if (ticks_x != 0) {
387 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
388 abs(ticks_x));
391 XFlush(display_);
394 void InputInjectorLinux::Core::InitMouseButtonMap() {
395 // TODO(rmsousa): Run this on global/device mapping change events.
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;
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;
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;
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;
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;
444 XFreeDeviceList(devices);
446 if (!device_found) {
447 HOST_LOG << "Cannot find XTest device.";
448 return;
451 XDevice* device = XOpenDevice(display_, device_id);
452 if (!device) {
453 LOG(ERROR) << "Cannot open XTest device.";
454 return;
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;
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;
467 XCloseDevice(display_, device);
470 int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber(
471 MouseEvent::MouseButton button) {
472 switch (button) {
473 case MouseEvent::BUTTON_LEFT:
474 return pointer_button_map_[0];
476 case MouseEvent::BUTTON_RIGHT:
477 return pointer_button_map_[2];
479 case MouseEvent::BUTTON_MIDDLE:
480 return pointer_button_map_[1];
482 case MouseEvent::BUTTON_UNDEFINED:
483 default:
484 return -1;
488 int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
489 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
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]);
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;
507 InitMouseButtonMap();
509 clipboard_->Start(client_clipboard.Pass());
512 void InputInjectorLinux::Core::Stop() {
513 if (!task_runner_->BelongsToCurrentThread()) {
514 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this));
515 return;
518 clipboard_->Stop();
521 } // namespace
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>();
533 } // namespace remoting