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/extensions/XInput.h> 8#include <X11/extensions/XTest.h> 9#include <X11/Xlib.h> 10#include <X11/XKBlib.h> 11 12#include <set> 13 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" 26 27namespace remoting { 28 29namespace { 30 31using protocol::ClipboardEvent; 32using protocol::KeyEvent; 33using protocol::TextEvent; 34using protocol::MouseEvent; 35 36bool FindKeycodeForKeySym(Display* display, 37 KeySym key_sym, 38 uint32_t* keycode, 39 uint32_t* modifiers) { 40 *keycode = XKeysymToKeycode(display, key_sym); 41 42 const uint32_t kModifiersToTry[] = { 43 0, 44 ShiftMask, 45 Mod2Mask, 46 Mod3Mask, 47 Mod4Mask, 48 ShiftMask | Mod2Mask, 49 ShiftMask | Mod3Mask, 50 ShiftMask | Mod4Mask, 51 }; 52 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; 61 } 62 } 63 64 return false; 65} 66 67// Finds a keycode and set of modifiers that generate character with the 68// specified |code_point|. 69bool 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); 75 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; 80 } 81 } 82 83 return false; 84} 85 86// Pixel-to-wheel-ticks conversion ratio used by GTK. 87// From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp . 88const float kWheelTicksPerPixel = 3.0f / 160.0f; 89 90// A class to generate events on Linux. 91class InputInjectorLinux : public InputInjector { 92 public: 93 explicit InputInjectorLinux( 94 scoped_refptr<base::SingleThreadTaskRunner> task_runner); 95 virtual ~InputInjectorLinux(); 96 97 bool Init(); 98 99 // Clipboard stub interface. 100 virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE; 101 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; 106 107 // InputInjector interface. 108 virtual void Start( 109 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; 110 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); 116 117 bool Init(); 118 119 // Mirrors the ClipboardStub interface. 120 void InjectClipboardEvent(const ClipboardEvent& event); 121 122 // Mirrors the InputStub interface. 123 void InjectKeyEvent(const KeyEvent& event); 124 void InjectTextEvent(const TextEvent& event); 125 void InjectMouseEvent(const MouseEvent& event); 126 127 // Mirrors the InputInjector interface. 128 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard); 129 130 void Stop(); 131 132 private: 133 friend class base::RefCountedThreadSafe<Core>; 134 virtual ~Core(); 135 136 void InitClipboard(); 137 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(); 143 144 // Enables or disables keyboard auto-repeat globally. 145 void SetAutoRepeatEnabled(bool enabled); 146 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); 154 155 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 156 157 std::set<int> pressed_keys_; 158 webrtc::DesktopVector latest_mouse_position_; 159 float wheel_ticks_x_; 160 float wheel_ticks_y_; 161 162 // X11 graphics context. 163 Display* display_; 164 Window root_window_; 165 166 int test_event_base_; 167 int test_error_base_; 168 169 // Number of buttons we support. 170 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right. 171 static const int kNumPointerButtons = 7; 172 173 int pointer_button_map_[kNumPointerButtons]; 174 175 scoped_ptr<Clipboard> clipboard_; 176 177 bool saved_auto_repeat_enabled_; 178 179 DISALLOW_COPY_AND_ASSIGN(Core); 180 }; 181 182 scoped_refptr<Core> core_; 183 184 DISALLOW_COPY_AND_ASSIGN(InputInjectorLinux); 185}; 186 187InputInjectorLinux::InputInjectorLinux( 188 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { 189 core_ = new Core(task_runner); 190} 191 192InputInjectorLinux::~InputInjectorLinux() { 193 core_->Stop(); 194} 195 196bool InputInjectorLinux::Init() { 197 return core_->Init(); 198} 199 200void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) { 201 core_->InjectClipboardEvent(event); 202} 203 204void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) { 205 core_->InjectKeyEvent(event); 206} 207 208void InputInjectorLinux::InjectTextEvent(const TextEvent& event) { 209 core_->InjectTextEvent(event); 210} 211 212void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) { 213 core_->InjectMouseEvent(event); 214} 215 216void InputInjectorLinux::Start( 217 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 218 core_->Start(client_clipboard.Pass()); 219} 220 221InputInjectorLinux::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) { 230} 231 232bool InputInjectorLinux::Core::Init() { 233 CHECK(display_); 234 235 if (!task_runner_->BelongsToCurrentThread()) 236 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this)); 237 238 root_window_ = RootWindow(display_, DefaultScreen(display_)); 239 if (root_window_ == BadValue) { 240 LOG(ERROR) << "Unable to get the root window"; 241 return false; 242 } 243 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; 251 } 252 InitMouseButtonMap(); 253 return true; 254} 255 256void 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; 262 } 263 264 // |clipboard_| will ignore unknown MIME-types, and verify the data's format. 265 clipboard_->InjectClipboardEvent(event); 266} 267 268void 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; 272 273 if (!task_runner_->BelongsToCurrentThread()) { 274 task_runner_->PostTask(FROM_HERE, 275 base::Bind(&Core::InjectKeyEvent, this, event)); 276 return; 277 } 278 279 int keycode = 280 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode()); 281 282 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() 283 << " to keycode: " << keycode << std::dec; 284 285 // Ignore events which can't be mapped. 286 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode()) 287 return; 288 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); 294 } 295 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); 302 } 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); 310 } 311 } 312 313 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime); 314 XFlush(display_); 315} 316 317void 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; 322 } 323 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; 330 } 331 332 uint32_t keycode; 333 uint32_t modifiers; 334 if (!FindKeycodeForUnicode(display_, code_point, &keycode, &modifiers)) 335 continue; 336 337 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, modifiers); 338 339 XTestFakeKeyEvent(display_, keycode, True, CurrentTime); 340 XTestFakeKeyEvent(display_, keycode, False, CurrentTime); 341 342 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, 0); 343 } 344 345 XFlush(display_); 346} 347 348InputInjectorLinux::Core::~Core() { 349 CHECK(pressed_keys_.empty()); 350} 351 352void InputInjectorLinux::Core::InitClipboard() { 353 DCHECK(task_runner_->BelongsToCurrentThread()); 354 clipboard_ = Clipboard::Create(); 355} 356 357bool 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; 362 } 363 return state.global_auto_repeat == AutoRepeatModeOn; 364} 365 366void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) { 367 XKeyboardControl control; 368 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff; 369 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control); 370} 371 372void InputInjectorLinux::Core::InjectScrollWheelClicks(int button, int count) { 373 if (button < 0) { 374 LOG(WARNING) << "Ignoring unmapped scroll wheel button"; 375 return; 376 } 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); 381 } 382} 383 384void 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; 389 } 390 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); 399 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; 410 } 411 412 if (inject_motion) { 413 latest_mouse_position_.set(std::max(0, new_mouse_position.x()), 414 std::max(0, new_mouse_position.y())); 415 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); 422 } 423 } 424 425 if (event.has_button() && event.has_button_down()) { 426 int button_number = MouseButtonToX11ButtonNumber(event.button()); 427 428 if (button_number < 0) { 429 LOG(WARNING) << "Ignoring unknown button type: " << event.button(); 430 return; 431 } 432 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); 439 } 440 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; 453 } 454 if (ticks_y != 0) { 455 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y), 456 abs(ticks_y)); 457 } 458 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; 466 } 467 if (ticks_x != 0) { 468 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x), 469 abs(ticks_x)); 470 } 471 472 XFlush(display_); 473} 474 475void InputInjectorLinux::Core::InitMouseButtonMap() { 476 // TODO(rmsousa): Run this on global/device mapping change events. 477 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; 488 } 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; 493 } 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; 497 } 498 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; 505 } 506 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; 523 } 524 } 525 XFreeDeviceList(devices); 526 527 if (!device_found) { 528 HOST_LOG << "Cannot find XTest device."; 529 return; 530 } 531 532 XDevice* device = XOpenDevice(display_, device_id); 533 if (!device) { 534 LOG(ERROR) << "Cannot open XTest device."; 535 return; 536 } 537 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; 542 } 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; 547 548 XCloseDevice(display_, device); 549} 550 551int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber( 552 MouseEvent::MouseButton button) { 553 switch (button) { 554 case MouseEvent::BUTTON_LEFT: 555 return pointer_button_map_[0]; 556 557 case MouseEvent::BUTTON_RIGHT: 558 return pointer_button_map_[2]; 559 560 case MouseEvent::BUTTON_MIDDLE: 561 return pointer_button_map_[1]; 562 563 case MouseEvent::BUTTON_UNDEFINED: 564 default: 565 return -1; 566 } 567} 568 569int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) { 570 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]); 571} 572 573int 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]); 577} 578 579void 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; 586 } 587 588 InitMouseButtonMap(); 589 590 clipboard_->Start(client_clipboard.Pass()); 591} 592 593void InputInjectorLinux::Core::Stop() { 594 if (!task_runner_->BelongsToCurrentThread()) { 595 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); 596 return; 597 } 598 599 clipboard_->Stop(); 600} 601 602} // namespace 603 604scoped_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>(); 612} 613 614} // namespace remoting 615