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>
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"
28 using protocol::ClipboardEvent
;
29 using protocol::KeyEvent
;
30 using protocol::MouseEvent
;
32 // Pixel-to-wheel-ticks conversion ratio used by GTK.
33 // From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp .
34 const float kWheelTicksPerPixel
= 3.0f
/ 160.0f
;
36 // A class to generate events on Linux.
37 class InputInjectorLinux
: public InputInjector
{
39 explicit InputInjectorLinux(
40 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
41 virtual ~InputInjectorLinux();
45 // Clipboard stub interface.
46 virtual void InjectClipboardEvent(const ClipboardEvent
& event
) OVERRIDE
;
48 // InputStub interface.
49 virtual void InjectKeyEvent(const KeyEvent
& event
) OVERRIDE
;
50 virtual void InjectMouseEvent(const MouseEvent
& event
) OVERRIDE
;
52 // InputInjector interface.
54 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) OVERRIDE
;
57 // The actual implementation resides in InputInjectorLinux::Core class.
58 class Core
: public base::RefCountedThreadSafe
<Core
> {
60 explicit Core(scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
);
64 // Mirrors the ClipboardStub interface.
65 void InjectClipboardEvent(const ClipboardEvent
& event
);
67 // Mirrors the InputStub interface.
68 void InjectKeyEvent(const KeyEvent
& event
);
69 void InjectMouseEvent(const MouseEvent
& event
);
71 // Mirrors the InputInjector interface.
72 void Start(scoped_ptr
<protocol::ClipboardStub
> client_clipboard
);
77 friend class base::RefCountedThreadSafe
<Core
>;
82 // Queries whether keyboard auto-repeat is globally enabled. This is used
83 // to decide whether to temporarily disable then restore this setting. If
84 // auto-repeat has already been disabled, this class should leave it
86 bool IsAutoRepeatEnabled();
88 // Enables or disables keyboard auto-repeat globally.
89 void SetAutoRepeatEnabled(bool enabled
);
91 void InjectScrollWheelClicks(int button
, int count
);
92 // Compensates for global button mappings and resets the XTest device
94 void InitMouseButtonMap();
95 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button
);
96 int HorizontalScrollWheelToX11ButtonNumber(int dx
);
97 int VerticalScrollWheelToX11ButtonNumber(int dy
);
99 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
101 std::set
<int> pressed_keys_
;
102 webrtc::DesktopVector latest_mouse_position_
;
103 float wheel_ticks_x_
;
104 float wheel_ticks_y_
;
106 // X11 graphics context.
110 int test_event_base_
;
111 int test_error_base_
;
113 // Number of buttons we support.
114 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right.
115 static const int kNumPointerButtons
= 7;
117 int pointer_button_map_
[kNumPointerButtons
];
119 scoped_ptr
<Clipboard
> clipboard_
;
121 bool saved_auto_repeat_enabled_
;
123 DISALLOW_COPY_AND_ASSIGN(Core
);
126 scoped_refptr
<Core
> core_
;
128 DISALLOW_COPY_AND_ASSIGN(InputInjectorLinux
);
131 InputInjectorLinux::InputInjectorLinux(
132 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
) {
133 core_
= new Core(task_runner
);
135 InputInjectorLinux::~InputInjectorLinux() {
139 bool InputInjectorLinux::Init() {
140 return core_
->Init();
143 void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent
& event
) {
144 core_
->InjectClipboardEvent(event
);
147 void InputInjectorLinux::InjectKeyEvent(const KeyEvent
& event
) {
148 core_
->InjectKeyEvent(event
);
151 void InputInjectorLinux::InjectMouseEvent(const MouseEvent
& event
) {
152 core_
->InjectMouseEvent(event
);
155 void InputInjectorLinux::Start(
156 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
157 core_
->Start(client_clipboard
.Pass());
160 InputInjectorLinux::Core::Core(
161 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
162 : task_runner_(task_runner
),
163 latest_mouse_position_(-1, -1),
164 wheel_ticks_x_(0.0f
),
165 wheel_ticks_y_(0.0f
),
166 display_(XOpenDisplay(NULL
)),
167 root_window_(BadValue
),
168 saved_auto_repeat_enabled_(false) {
171 bool InputInjectorLinux::Core::Init() {
174 if (!task_runner_
->BelongsToCurrentThread())
175 task_runner_
->PostTask(FROM_HERE
, base::Bind(&Core::InitClipboard
, this));
177 root_window_
= RootWindow(display_
, DefaultScreen(display_
));
178 if (root_window_
== BadValue
) {
179 LOG(ERROR
) << "Unable to get the root window";
183 // TODO(ajwong): Do we want to check the major/minor version at all for XTest?
186 if (!XTestQueryExtension(display_
, &test_event_base_
, &test_error_base_
,
188 LOG(ERROR
) << "Server does not support XTest.";
191 InitMouseButtonMap();
195 void InputInjectorLinux::Core::InjectClipboardEvent(
196 const ClipboardEvent
& event
) {
197 if (!task_runner_
->BelongsToCurrentThread()) {
198 task_runner_
->PostTask(
199 FROM_HERE
, base::Bind(&Core::InjectClipboardEvent
, this, event
));
203 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
204 clipboard_
->InjectClipboardEvent(event
);
207 void InputInjectorLinux::Core::InjectKeyEvent(const KeyEvent
& event
) {
208 // HostEventDispatcher should filter events missing the pressed field.
209 if (!event
.has_pressed() || !event
.has_usb_keycode())
212 if (!task_runner_
->BelongsToCurrentThread()) {
213 task_runner_
->PostTask(FROM_HERE
,
214 base::Bind(&Core::InjectKeyEvent
, this, event
));
218 ui::KeycodeConverter
* key_converter
= ui::KeycodeConverter::GetInstance();
219 int keycode
= key_converter
->UsbKeycodeToNativeKeycode(event
.usb_keycode());
221 VLOG(3) << "Converting USB keycode: " << std::hex
<< event
.usb_keycode()
222 << " to keycode: " << keycode
<< std::dec
;
224 // Ignore events which can't be mapped.
225 if (keycode
== key_converter
->InvalidNativeKeycode())
228 if (event
.pressed()) {
229 if (pressed_keys_
.find(keycode
) != pressed_keys_
.end()) {
230 // Key is already held down, so lift the key up to ensure this repeated
231 // press takes effect.
232 XTestFakeKeyEvent(display_
, keycode
, False
, CurrentTime
);
235 if (pressed_keys_
.empty()) {
236 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat
237 // if network congestion delays the key-up event from the client.
238 saved_auto_repeat_enabled_
= IsAutoRepeatEnabled();
239 if (saved_auto_repeat_enabled_
)
240 SetAutoRepeatEnabled(false);
242 pressed_keys_
.insert(keycode
);
244 pressed_keys_
.erase(keycode
);
245 if (pressed_keys_
.empty()) {
246 // Re-enable auto-repeat, if necessary, when all keys are released.
247 if (saved_auto_repeat_enabled_
)
248 SetAutoRepeatEnabled(true);
252 XTestFakeKeyEvent(display_
, keycode
, event
.pressed(), CurrentTime
);
256 InputInjectorLinux::Core::~Core() {
257 CHECK(pressed_keys_
.empty());
260 void InputInjectorLinux::Core::InitClipboard() {
261 DCHECK(task_runner_
->BelongsToCurrentThread());
262 clipboard_
= Clipboard::Create();
265 bool InputInjectorLinux::Core::IsAutoRepeatEnabled() {
266 XKeyboardState state
;
267 if (!XGetKeyboardControl(display_
, &state
)) {
268 LOG(ERROR
) << "Failed to get keyboard auto-repeat status, assuming ON.";
271 return state
.global_auto_repeat
== AutoRepeatModeOn
;
274 void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode
) {
275 XKeyboardControl control
;
276 control
.auto_repeat_mode
= mode
? AutoRepeatModeOn
: AutoRepeatModeOff
;
277 XChangeKeyboardControl(display_
, KBAutoRepeatMode
, &control
);
280 void InputInjectorLinux::Core::InjectScrollWheelClicks(int button
, int count
) {
282 LOG(WARNING
) << "Ignoring unmapped scroll wheel button";
285 for (int i
= 0; i
< count
; i
++) {
286 // Generate a button-down and a button-up to simulate a wheel click.
287 XTestFakeButtonEvent(display_
, button
, true, CurrentTime
);
288 XTestFakeButtonEvent(display_
, button
, false, CurrentTime
);
292 void InputInjectorLinux::Core::InjectMouseEvent(const MouseEvent
& event
) {
293 if (!task_runner_
->BelongsToCurrentThread()) {
294 task_runner_
->PostTask(FROM_HERE
,
295 base::Bind(&Core::InjectMouseEvent
, this, event
));
299 if (event
.has_delta_x() &&
300 event
.has_delta_y() &&
301 (event
.delta_x() != 0 || event
.delta_y() != 0)) {
302 latest_mouse_position_
.set(-1, -1);
303 VLOG(3) << "Moving mouse by " << event
.delta_x() << "," << event
.delta_y();
304 XTestFakeRelativeMotionEvent(display_
,
305 event
.delta_x(), event
.delta_y(),
308 } else if (event
.has_x() && event
.has_y()) {
309 // Injecting a motion event immediately before a button release results in
310 // a MotionNotify even if the mouse position hasn't changed, which confuses
311 // apps which assume MotionNotify implies movement. See crbug.com/138075.
312 bool inject_motion
= true;
313 webrtc::DesktopVector
new_mouse_position(
314 webrtc::DesktopVector(event
.x(), event
.y()));
315 if (event
.has_button() && event
.has_button_down() && !event
.button_down()) {
316 if (new_mouse_position
.equals(latest_mouse_position_
))
317 inject_motion
= false;
321 latest_mouse_position_
.set(std::max(0, new_mouse_position
.x()),
322 std::max(0, new_mouse_position
.y()));
324 VLOG(3) << "Moving mouse to " << latest_mouse_position_
.x()
325 << "," << latest_mouse_position_
.y();
326 XTestFakeMotionEvent(display_
, DefaultScreen(display_
),
327 latest_mouse_position_
.x(),
328 latest_mouse_position_
.y(),
333 if (event
.has_button() && event
.has_button_down()) {
334 int button_number
= MouseButtonToX11ButtonNumber(event
.button());
336 if (button_number
< 0) {
337 LOG(WARNING
) << "Ignoring unknown button type: " << event
.button();
341 VLOG(3) << "Button " << event
.button()
342 << " received, sending "
343 << (event
.button_down() ? "down " : "up ")
345 XTestFakeButtonEvent(display_
, button_number
, event
.button_down(),
349 // Older client plugins always send scroll events in pixels, which
350 // must be accumulated host-side. Recent client plugins send both
351 // pixels and ticks with every scroll event, allowing the host to
352 // choose the best model on a per-platform basis. Since we can only
353 // inject ticks on Linux, use them if available.
355 if (event
.has_wheel_ticks_y()) {
356 ticks_y
= event
.wheel_ticks_y();
357 } else if (event
.has_wheel_delta_y()) {
358 wheel_ticks_y_
+= event
.wheel_delta_y() * kWheelTicksPerPixel
;
359 ticks_y
= static_cast<int>(wheel_ticks_y_
);
360 wheel_ticks_y_
-= ticks_y
;
363 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y
),
368 if (event
.has_wheel_ticks_x()) {
369 ticks_x
= event
.wheel_ticks_x();
370 } else if (event
.has_wheel_delta_x()) {
371 wheel_ticks_x_
+= event
.wheel_delta_x() * kWheelTicksPerPixel
;
372 ticks_x
= static_cast<int>(wheel_ticks_x_
);
373 wheel_ticks_x_
-= ticks_x
;
376 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x
),
383 void InputInjectorLinux::Core::InitMouseButtonMap() {
384 // TODO(rmsousa): Run this on global/device mapping change events.
386 // Do not touch global pointer mapping, since this may affect the local user.
387 // Instead, try to work around it by reversing the mapping.
388 // Note that if a user has a global mapping that completely disables a button
389 // (by assigning 0 to it), we won't be able to inject it.
390 int num_buttons
= XGetPointerMapping(display_
, NULL
, 0);
391 scoped_ptr
<unsigned char[]> pointer_mapping(new unsigned char[num_buttons
]);
392 num_buttons
= XGetPointerMapping(display_
, pointer_mapping
.get(),
394 for (int i
= 0; i
< kNumPointerButtons
; i
++) {
395 pointer_button_map_
[i
] = -1;
397 for (int i
= 0; i
< num_buttons
; i
++) {
398 // Reverse the mapping.
399 if (pointer_mapping
[i
] > 0 && pointer_mapping
[i
] <= kNumPointerButtons
)
400 pointer_button_map_
[pointer_mapping
[i
] - 1] = i
+ 1;
402 for (int i
= 0; i
< kNumPointerButtons
; i
++) {
403 if (pointer_button_map_
[i
] == -1)
404 LOG(ERROR
) << "Global pointer mapping does not support button " << i
+ 1;
407 int opcode
, event
, error
;
408 if (!XQueryExtension(display_
, "XInputExtension", &opcode
, &event
, &error
)) {
409 // If XInput is not available, we're done. But it would be very unusual to
410 // have a server that supports XTest but not XInput, so log it as an error.
411 LOG(ERROR
) << "X Input extension not available: " << error
;
415 // Make sure the XTEST XInput pointer device mapping is trivial. It should be
416 // safe to reset this mapping, as it won't affect the user's local devices.
417 // In fact, the reason why we do this is because an old gnome-settings-daemon
418 // may have mistakenly applied left-handed preferences to the XTEST device.
420 bool device_found
= false;
422 XDeviceInfo
* devices
;
423 devices
= XListInputDevices(display_
, &num_devices
);
424 for (int i
= 0; i
< num_devices
; i
++) {
425 XDeviceInfo
* device_info
= &devices
[i
];
426 if (device_info
->use
== IsXExtensionPointer
&&
427 strcmp(device_info
->name
, "Virtual core XTEST pointer") == 0) {
428 device_id
= device_info
->id
;
433 XFreeDeviceList(devices
);
436 HOST_LOG
<< "Cannot find XTest device.";
440 XDevice
* device
= XOpenDevice(display_
, device_id
);
442 LOG(ERROR
) << "Cannot open XTest device.";
446 int num_device_buttons
= XGetDeviceButtonMapping(display_
, device
, NULL
, 0);
447 scoped_ptr
<unsigned char[]> button_mapping(new unsigned char[num_buttons
]);
448 for (int i
= 0; i
< num_device_buttons
; i
++) {
449 button_mapping
[i
] = i
+ 1;
451 error
= XSetDeviceButtonMapping(display_
, device
, button_mapping
.get(),
453 if (error
!= Success
)
454 LOG(ERROR
) << "Failed to set XTest device button mapping: " << error
;
456 XCloseDevice(display_
, device
);
459 int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber(
460 MouseEvent::MouseButton button
) {
462 case MouseEvent::BUTTON_LEFT
:
463 return pointer_button_map_
[0];
465 case MouseEvent::BUTTON_RIGHT
:
466 return pointer_button_map_
[2];
468 case MouseEvent::BUTTON_MIDDLE
:
469 return pointer_button_map_
[1];
471 case MouseEvent::BUTTON_UNDEFINED
:
477 int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx
) {
478 return (dx
> 0 ? pointer_button_map_
[5] : pointer_button_map_
[6]);
481 int InputInjectorLinux::Core::VerticalScrollWheelToX11ButtonNumber(int dy
) {
482 // Positive y-values are wheel scroll-up events (button 4), negative y-values
483 // are wheel scroll-down events (button 5).
484 return (dy
> 0 ? pointer_button_map_
[3] : pointer_button_map_
[4]);
487 void InputInjectorLinux::Core::Start(
488 scoped_ptr
<protocol::ClipboardStub
> client_clipboard
) {
489 if (!task_runner_
->BelongsToCurrentThread()) {
490 task_runner_
->PostTask(
492 base::Bind(&Core::Start
, this, base::Passed(&client_clipboard
)));
496 InitMouseButtonMap();
498 clipboard_
->Start(client_clipboard
.Pass());
501 void InputInjectorLinux::Core::Stop() {
502 if (!task_runner_
->BelongsToCurrentThread()) {
503 task_runner_
->PostTask(FROM_HERE
, base::Bind(&Core::Stop
, this));
512 scoped_ptr
<InputInjector
> InputInjector::Create(
513 scoped_refptr
<base::SingleThreadTaskRunner
> main_task_runner
,
514 scoped_refptr
<base::SingleThreadTaskRunner
> ui_task_runner
) {
515 scoped_ptr
<InputInjectorLinux
> injector(
516 new InputInjectorLinux(main_task_runner
));
517 if (!injector
->Init())
518 return scoped_ptr
<InputInjector
>();
519 return injector
.PassAs
<InputInjector
>();
522 } // namespace remoting