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/Xlib.h> 8 #include <X11/extensions/XTest.h> 9 #include <X11/extensions/XInput.h> 10 11 #include <set> 12 13 #include "base/basictypes.h" 14 #include "base/bind.h" 15 #include "base/compiler_specific.h" 16 #include "base/location.h" 17 #include "base/logging.h" 18 #include "base/single_thread_task_runner.h" 19 #include "remoting/host/clipboard.h" 20 #include "remoting/proto/internal.pb.h" 21 #include "third_party/skia/include/core/SkPoint.h" 22 23 namespace remoting { 24 25 namespace { 26 27 using protocol::ClipboardEvent; 28 using protocol::KeyEvent; 29 using protocol::MouseEvent; 30 31 // USB to XKB keycode map table. 32 #define USB_KEYMAP(usb, xkb, win, mac) {usb, xkb} 33 #include "ui/base/keycodes/usb_keycode_map.h" 34 #undef USB_KEYMAP 35 36 // Pixel-to-wheel-ticks conversion ratio used by GTK. 37 // From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp . 38 const float kWheelTicksPerPixel = 3.0f / 160.0f; 39 40 // A class to generate events on Linux. 41 class InputInjectorLinux : public InputInjector { 42 public: 43 explicit InputInjectorLinux( 44 scoped_refptr<base::SingleThreadTaskRunner> task_runner); 45 virtual ~InputInjectorLinux(); 46 47 bool Init(); 48 49 // Clipboard stub interface. 50 virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE; 51 52 // InputStub interface. 53 virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE; 54 virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE; 55 56 // InputInjector interface. 57 virtual void Start( 58 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; 59 60 private: 61 // The actual implementation resides in InputInjectorLinux::Core class. 62 class Core : public base::RefCountedThreadSafe<Core> { 63 public: 64 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner); 65 66 bool Init(); 67 68 // Mirrors the ClipboardStub interface. 69 void InjectClipboardEvent(const ClipboardEvent& event); 70 71 // Mirrors the InputStub interface. 72 void InjectKeyEvent(const KeyEvent& event); 73 void InjectMouseEvent(const MouseEvent& event); 74 75 // Mirrors the InputInjector interface. 76 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard); 77 78 void Stop(); 79 80 private: 81 friend class base::RefCountedThreadSafe<Core>; 82 virtual ~Core(); 83 84 void InitClipboard(); 85 86 // Queries whether keyboard auto-repeat is globally enabled. This is used 87 // to decide whether to temporarily disable then restore this setting. If 88 // auto-repeat has already been disabled, this class should leave it 89 // untouched. 90 bool IsAutoRepeatEnabled(); 91 92 // Enables or disables keyboard auto-repeat globally. 93 void SetAutoRepeatEnabled(bool enabled); 94 95 void InjectScrollWheelClicks(int button, int count); 96 // Compensates for global button mappings and resets the XTest device 97 // mapping. 98 void InitMouseButtonMap(); 99 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button); 100 int HorizontalScrollWheelToX11ButtonNumber(int dx); 101 int VerticalScrollWheelToX11ButtonNumber(int dy); 102 103 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 104 105 std::set<int> pressed_keys_; 106 SkIPoint latest_mouse_position_; 107 float wheel_ticks_x_; 108 float wheel_ticks_y_; 109 110 // X11 graphics context. 111 Display* display_; 112 Window root_window_; 113 114 int test_event_base_; 115 int test_error_base_; 116 117 // Number of buttons we support. 118 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right. 119 static const int kNumPointerButtons = 7; 120 121 int pointer_button_map_[kNumPointerButtons]; 122 123 scoped_ptr<Clipboard> clipboard_; 124 125 bool saved_auto_repeat_enabled_; 126 127 DISALLOW_COPY_AND_ASSIGN(Core); 128 }; 129 130 scoped_refptr<Core> core_; 131 132 DISALLOW_COPY_AND_ASSIGN(InputInjectorLinux); 133 }; 134 135 InputInjectorLinux::InputInjectorLinux( 136 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { 137 core_ = new Core(task_runner); 138 } 139 InputInjectorLinux::~InputInjectorLinux() { 140 core_->Stop(); 141 } 142 143 bool InputInjectorLinux::Init() { 144 return core_->Init(); 145 } 146 147 void InputInjectorLinux::InjectClipboardEvent(const ClipboardEvent& event) { 148 core_->InjectClipboardEvent(event); 149 } 150 151 void InputInjectorLinux::InjectKeyEvent(const KeyEvent& event) { 152 core_->InjectKeyEvent(event); 153 } 154 155 void InputInjectorLinux::InjectMouseEvent(const MouseEvent& event) { 156 core_->InjectMouseEvent(event); 157 } 158 159 void InputInjectorLinux::Start( 160 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 161 core_->Start(client_clipboard.Pass()); 162 } 163 164 InputInjectorLinux::Core::Core( 165 scoped_refptr<base::SingleThreadTaskRunner> task_runner) 166 : task_runner_(task_runner), 167 latest_mouse_position_(SkIPoint::Make(-1, -1)), 168 wheel_ticks_x_(0.0f), 169 wheel_ticks_y_(0.0f), 170 display_(XOpenDisplay(NULL)), 171 root_window_(BadValue), 172 saved_auto_repeat_enabled_(false) { 173 } 174 175 bool InputInjectorLinux::Core::Init() { 176 CHECK(display_); 177 178 if (!task_runner_->BelongsToCurrentThread()) 179 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this)); 180 181 root_window_ = RootWindow(display_, DefaultScreen(display_)); 182 if (root_window_ == BadValue) { 183 LOG(ERROR) << "Unable to get the root window"; 184 return false; 185 } 186 187 // TODO(ajwong): Do we want to check the major/minor version at all for XTest? 188 int major = 0; 189 int minor = 0; 190 if (!XTestQueryExtension(display_, &test_event_base_, &test_error_base_, 191 &major, &minor)) { 192 LOG(ERROR) << "Server does not support XTest."; 193 return false; 194 } 195 InitMouseButtonMap(); 196 return true; 197 } 198 199 void InputInjectorLinux::Core::InjectClipboardEvent( 200 const ClipboardEvent& event) { 201 if (!task_runner_->BelongsToCurrentThread()) { 202 task_runner_->PostTask( 203 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event)); 204 return; 205 } 206 207 // |clipboard_| will ignore unknown MIME-types, and verify the data's format. 208 clipboard_->InjectClipboardEvent(event); 209 } 210 211 void InputInjectorLinux::Core::InjectKeyEvent(const KeyEvent& event) { 212 // HostEventDispatcher should filter events missing the pressed field. 213 if (!event.has_pressed() || !event.has_usb_keycode()) 214 return; 215 216 if (!task_runner_->BelongsToCurrentThread()) { 217 task_runner_->PostTask(FROM_HERE, 218 base::Bind(&Core::InjectKeyEvent, this, event)); 219 return; 220 } 221 222 int keycode = UsbKeycodeToNativeKeycode(event.usb_keycode()); 223 224 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() 225 << " to keycode: " << keycode << std::dec; 226 227 // Ignore events which can't be mapped. 228 if (keycode == InvalidNativeKeycode()) 229 return; 230 231 if (event.pressed()) { 232 if (pressed_keys_.find(keycode) != pressed_keys_.end()) { 233 // Key is already held down, so lift the key up to ensure this repeated 234 // press takes effect. 235 XTestFakeKeyEvent(display_, keycode, False, CurrentTime); 236 } 237 238 if (pressed_keys_.empty()) { 239 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat 240 // if network congestion delays the key-up event from the client. 241 saved_auto_repeat_enabled_ = IsAutoRepeatEnabled(); 242 if (saved_auto_repeat_enabled_) 243 SetAutoRepeatEnabled(false); 244 } 245 pressed_keys_.insert(keycode); 246 } else { 247 pressed_keys_.erase(keycode); 248 if (pressed_keys_.empty()) { 249 // Re-enable auto-repeat, if necessary, when all keys are released. 250 if (saved_auto_repeat_enabled_) 251 SetAutoRepeatEnabled(true); 252 } 253 } 254 255 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime); 256 XFlush(display_); 257 } 258 259 InputInjectorLinux::Core::~Core() { 260 CHECK(pressed_keys_.empty()); 261 } 262 263 void InputInjectorLinux::Core::InitClipboard() { 264 DCHECK(task_runner_->BelongsToCurrentThread()); 265 clipboard_ = Clipboard::Create(); 266 } 267 268 bool InputInjectorLinux::Core::IsAutoRepeatEnabled() { 269 XKeyboardState state; 270 if (!XGetKeyboardControl(display_, &state)) { 271 LOG(ERROR) << "Failed to get keyboard auto-repeat status, assuming ON."; 272 return true; 273 } 274 return state.global_auto_repeat == AutoRepeatModeOn; 275 } 276 277 void InputInjectorLinux::Core::SetAutoRepeatEnabled(bool mode) { 278 XKeyboardControl control; 279 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff; 280 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control); 281 } 282 283 void InputInjectorLinux::Core::InjectScrollWheelClicks(int button, int count) { 284 if (button < 0) { 285 LOG(WARNING) << "Ignoring unmapped scroll wheel button"; 286 return; 287 } 288 for (int i = 0; i < count; i++) { 289 // Generate a button-down and a button-up to simulate a wheel click. 290 XTestFakeButtonEvent(display_, button, true, CurrentTime); 291 XTestFakeButtonEvent(display_, button, false, CurrentTime); 292 } 293 } 294 295 void InputInjectorLinux::Core::InjectMouseEvent(const MouseEvent& event) { 296 if (!task_runner_->BelongsToCurrentThread()) { 297 task_runner_->PostTask(FROM_HERE, 298 base::Bind(&Core::InjectMouseEvent, this, event)); 299 return; 300 } 301 302 if (event.has_x() && event.has_y()) { 303 // Injecting a motion event immediately before a button release results in 304 // a MotionNotify even if the mouse position hasn't changed, which confuses 305 // apps which assume MotionNotify implies movement. See crbug.com/138075. 306 bool inject_motion = true; 307 SkIPoint new_mouse_position(SkIPoint::Make(event.x(), event.y())); 308 if (event.has_button() && event.has_button_down() && !event.button_down()) { 309 if (new_mouse_position == latest_mouse_position_) 310 inject_motion = false; 311 } 312 313 if (inject_motion) { 314 latest_mouse_position_ = 315 SkIPoint::Make(std::max(0, new_mouse_position.x()), 316 std::max(0, new_mouse_position.y())); 317 318 VLOG(3) << "Moving mouse to " << latest_mouse_position_.x() 319 << "," << latest_mouse_position_.y(); 320 XTestFakeMotionEvent(display_, DefaultScreen(display_), 321 latest_mouse_position_.x(), 322 latest_mouse_position_.y(), 323 CurrentTime); 324 } 325 } 326 327 if (event.has_button() && event.has_button_down()) { 328 int button_number = MouseButtonToX11ButtonNumber(event.button()); 329 330 if (button_number < 0) { 331 LOG(WARNING) << "Ignoring unknown button type: " << event.button(); 332 return; 333 } 334 335 VLOG(3) << "Button " << event.button() 336 << " received, sending " 337 << (event.button_down() ? "down " : "up ") 338 << button_number; 339 XTestFakeButtonEvent(display_, button_number, event.button_down(), 340 CurrentTime); 341 } 342 343 // Older client plugins always send scroll events in pixels, which 344 // must be accumulated host-side. Recent client plugins send both 345 // pixels and ticks with every scroll event, allowing the host to 346 // choose the best model on a per-platform basis. Since we can only 347 // inject ticks on Linux, use them if available. 348 int ticks_y = 0; 349 if (event.has_wheel_ticks_y()) { 350 ticks_y = event.wheel_ticks_y(); 351 } else if (event.has_wheel_delta_y()) { 352 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel; 353 ticks_y = static_cast<int>(wheel_ticks_y_); 354 wheel_ticks_y_ -= ticks_y; 355 } 356 if (ticks_y != 0) { 357 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y), 358 abs(ticks_y)); 359 } 360 361 int ticks_x = 0; 362 if (event.has_wheel_ticks_x()) { 363 ticks_x = event.wheel_ticks_x(); 364 } else if (event.has_wheel_delta_x()) { 365 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel; 366 ticks_x = static_cast<int>(wheel_ticks_x_); 367 wheel_ticks_x_ -= ticks_x; 368 } 369 if (ticks_x != 0) { 370 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x), 371 abs(ticks_x)); 372 } 373 374 XFlush(display_); 375 } 376 377 void InputInjectorLinux::Core::InitMouseButtonMap() { 378 // TODO(rmsousa): Run this on global/device mapping change events. 379 380 // Do not touch global pointer mapping, since this may affect the local user. 381 // Instead, try to work around it by reversing the mapping. 382 // Note that if a user has a global mapping that completely disables a button 383 // (by assigning 0 to it), we won't be able to inject it. 384 int num_buttons = XGetPointerMapping(display_, NULL, 0); 385 scoped_ptr<unsigned char[]> pointer_mapping(new unsigned char[num_buttons]); 386 num_buttons = XGetPointerMapping(display_, pointer_mapping.get(), 387 num_buttons); 388 for (int i = 0; i < kNumPointerButtons; i++) { 389 pointer_button_map_[i] = -1; 390 } 391 for (int i = 0; i < num_buttons; i++) { 392 // Reverse the mapping. 393 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons) 394 pointer_button_map_[pointer_mapping[i] - 1] = i + 1; 395 } 396 for (int i = 0; i < kNumPointerButtons; i++) { 397 if (pointer_button_map_[i] == -1) 398 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1; 399 } 400 401 int opcode, event, error; 402 if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) { 403 // If XInput is not available, we're done. But it would be very unusual to 404 // have a server that supports XTest but not XInput, so log it as an error. 405 LOG(ERROR) << "X Input extension not available: " << error; 406 return; 407 } 408 409 // Make sure the XTEST XInput pointer device mapping is trivial. It should be 410 // safe to reset this mapping, as it won't affect the user's local devices. 411 // In fact, the reason why we do this is because an old gnome-settings-daemon 412 // may have mistakenly applied left-handed preferences to the XTEST device. 413 XID device_id = 0; 414 bool device_found = false; 415 int num_devices; 416 XDeviceInfo* devices; 417 devices = XListInputDevices(display_, &num_devices); 418 for (int i = 0; i < num_devices; i++) { 419 XDeviceInfo* device_info = &devices[i]; 420 if (device_info->use == IsXExtensionPointer && 421 strcmp(device_info->name, "Virtual core XTEST pointer") == 0) { 422 device_id = device_info->id; 423 device_found = true; 424 break; 425 } 426 } 427 XFreeDeviceList(devices); 428 429 if (!device_found) { 430 LOG(INFO) << "Cannot find XTest device."; 431 return; 432 } 433 434 XDevice* device = XOpenDevice(display_, device_id); 435 if (!device) { 436 LOG(ERROR) << "Cannot open XTest device."; 437 return; 438 } 439 440 int num_device_buttons = XGetDeviceButtonMapping(display_, device, NULL, 0); 441 scoped_ptr<unsigned char[]> button_mapping(new unsigned char[num_buttons]); 442 for (int i = 0; i < num_device_buttons; i++) { 443 button_mapping[i] = i + 1; 444 } 445 error = XSetDeviceButtonMapping(display_, device, button_mapping.get(), 446 num_device_buttons); 447 if (error != Success) 448 LOG(ERROR) << "Failed to set XTest device button mapping: " << error; 449 450 XCloseDevice(display_, device); 451 } 452 453 int InputInjectorLinux::Core::MouseButtonToX11ButtonNumber( 454 MouseEvent::MouseButton button) { 455 switch (button) { 456 case MouseEvent::BUTTON_LEFT: 457 return pointer_button_map_[0]; 458 459 case MouseEvent::BUTTON_RIGHT: 460 return pointer_button_map_[2]; 461 462 case MouseEvent::BUTTON_MIDDLE: 463 return pointer_button_map_[1]; 464 465 case MouseEvent::BUTTON_UNDEFINED: 466 default: 467 return -1; 468 } 469 } 470 471 int InputInjectorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) { 472 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]); 473 } 474 475 int InputInjectorLinux::Core::VerticalScrollWheelToX11ButtonNumber(int dy) { 476 // Positive y-values are wheel scroll-up events (button 4), negative y-values 477 // are wheel scroll-down events (button 5). 478 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]); 479 } 480 481 void InputInjectorLinux::Core::Start( 482 scoped_ptr<protocol::ClipboardStub> client_clipboard) { 483 if (!task_runner_->BelongsToCurrentThread()) { 484 task_runner_->PostTask( 485 FROM_HERE, 486 base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); 487 return; 488 } 489 490 InitMouseButtonMap(); 491 492 clipboard_->Start(client_clipboard.Pass()); 493 } 494 495 void InputInjectorLinux::Core::Stop() { 496 if (!task_runner_->BelongsToCurrentThread()) { 497 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); 498 return; 499 } 500 501 clipboard_->Stop(); 502 } 503 504 } // namespace 505 506 scoped_ptr<InputInjector> InputInjector::Create( 507 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, 508 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { 509 scoped_ptr<InputInjectorLinux> injector( 510 new InputInjectorLinux(main_task_runner)); 511 if (!injector->Init()) 512 return scoped_ptr<InputInjector>(); 513 return injector.PassAs<InputInjector>(); 514 } 515 516 } // namespace remoting 517