| OLD | NEW |
| (Empty) |
| 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 | |
| 27 namespace remoting { | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 using protocol::ClipboardEvent; | |
| 32 using protocol::KeyEvent; | |
| 33 using protocol::TextEvent; | |
| 34 using protocol::MouseEvent; | |
| 35 | |
| 36 bool 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|. | |
| 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); | |
| 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 . | |
| 88 const float kWheelTicksPerPixel = 3.0f / 160.0f; | |
| 89 | |
| 90 // A class to generate events on X11. | |
| 91 class InputInjectorX11 : public InputInjector { | |
| 92 public: | |
| 93 explicit InputInjectorX11( | |
| 94 scoped_refptr<base::SingleThreadTaskRunner> task_runner); | |
| 95 ~InputInjectorX11() override; | |
| 96 | |
| 97 bool Init(); | |
| 98 | |
| 99 // Clipboard stub interface. | |
| 100 void InjectClipboardEvent(const ClipboardEvent& event) override; | |
| 101 | |
| 102 // InputStub interface. | |
| 103 void InjectKeyEvent(const KeyEvent& event) override; | |
| 104 void InjectTextEvent(const TextEvent& event) override; | |
| 105 void InjectMouseEvent(const MouseEvent& event) override; | |
| 106 | |
| 107 // InputInjector interface. | |
| 108 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard) override; | |
| 109 | |
| 110 private: | |
| 111 // The actual implementation resides in InputInjectorX11::Core class. | |
| 112 class Core : public base::RefCountedThreadSafe<Core> { | |
| 113 public: | |
| 114 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner); | |
| 115 | |
| 116 bool Init(); | |
| 117 | |
| 118 // Mirrors the ClipboardStub interface. | |
| 119 void InjectClipboardEvent(const ClipboardEvent& event); | |
| 120 | |
| 121 // Mirrors the InputStub interface. | |
| 122 void InjectKeyEvent(const KeyEvent& event); | |
| 123 void InjectTextEvent(const TextEvent& event); | |
| 124 void InjectMouseEvent(const MouseEvent& event); | |
| 125 | |
| 126 // Mirrors the InputInjector interface. | |
| 127 void Start(scoped_ptr<protocol::ClipboardStub> client_clipboard); | |
| 128 | |
| 129 void Stop(); | |
| 130 | |
| 131 private: | |
| 132 friend class base::RefCountedThreadSafe<Core>; | |
| 133 virtual ~Core(); | |
| 134 | |
| 135 void InitClipboard(); | |
| 136 | |
| 137 // Queries whether keyboard auto-repeat is globally enabled. This is used | |
| 138 // to decide whether to temporarily disable then restore this setting. If | |
| 139 // auto-repeat has already been disabled, this class should leave it | |
| 140 // untouched. | |
| 141 bool IsAutoRepeatEnabled(); | |
| 142 | |
| 143 // Enables or disables keyboard auto-repeat globally. | |
| 144 void SetAutoRepeatEnabled(bool enabled); | |
| 145 | |
| 146 void InjectScrollWheelClicks(int button, int count); | |
| 147 // Compensates for global button mappings and resets the XTest device | |
| 148 // mapping. | |
| 149 void InitMouseButtonMap(); | |
| 150 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button); | |
| 151 int HorizontalScrollWheelToX11ButtonNumber(int dx); | |
| 152 int VerticalScrollWheelToX11ButtonNumber(int dy); | |
| 153 | |
| 154 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | |
| 155 | |
| 156 std::set<int> pressed_keys_; | |
| 157 webrtc::DesktopVector latest_mouse_position_; | |
| 158 float wheel_ticks_x_; | |
| 159 float wheel_ticks_y_; | |
| 160 | |
| 161 // X11 graphics context. | |
| 162 Display* display_; | |
| 163 Window root_window_; | |
| 164 | |
| 165 int test_event_base_; | |
| 166 int test_error_base_; | |
| 167 | |
| 168 // Number of buttons we support. | |
| 169 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right. | |
| 170 static const int kNumPointerButtons = 7; | |
| 171 | |
| 172 int pointer_button_map_[kNumPointerButtons]; | |
| 173 | |
| 174 scoped_ptr<Clipboard> clipboard_; | |
| 175 | |
| 176 bool saved_auto_repeat_enabled_; | |
| 177 | |
| 178 DISALLOW_COPY_AND_ASSIGN(Core); | |
| 179 }; | |
| 180 | |
| 181 scoped_refptr<Core> core_; | |
| 182 | |
| 183 DISALLOW_COPY_AND_ASSIGN(InputInjectorX11); | |
| 184 }; | |
| 185 | |
| 186 InputInjectorX11::InputInjectorX11( | |
| 187 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { | |
| 188 core_ = new Core(task_runner); | |
| 189 } | |
| 190 | |
| 191 InputInjectorX11::~InputInjectorX11() { | |
| 192 core_->Stop(); | |
| 193 } | |
| 194 | |
| 195 bool InputInjectorX11::Init() { | |
| 196 return core_->Init(); | |
| 197 } | |
| 198 | |
| 199 void InputInjectorX11::InjectClipboardEvent(const ClipboardEvent& event) { | |
| 200 core_->InjectClipboardEvent(event); | |
| 201 } | |
| 202 | |
| 203 void InputInjectorX11::InjectKeyEvent(const KeyEvent& event) { | |
| 204 core_->InjectKeyEvent(event); | |
| 205 } | |
| 206 | |
| 207 void InputInjectorX11::InjectTextEvent(const TextEvent& event) { | |
| 208 core_->InjectTextEvent(event); | |
| 209 } | |
| 210 | |
| 211 void InputInjectorX11::InjectMouseEvent(const MouseEvent& event) { | |
| 212 core_->InjectMouseEvent(event); | |
| 213 } | |
| 214 | |
| 215 void InputInjectorX11::Start( | |
| 216 scoped_ptr<protocol::ClipboardStub> client_clipboard) { | |
| 217 core_->Start(client_clipboard.Pass()); | |
| 218 } | |
| 219 | |
| 220 InputInjectorX11::Core::Core( | |
| 221 scoped_refptr<base::SingleThreadTaskRunner> task_runner) | |
| 222 : task_runner_(task_runner), | |
| 223 latest_mouse_position_(-1, -1), | |
| 224 wheel_ticks_x_(0.0f), | |
| 225 wheel_ticks_y_(0.0f), | |
| 226 display_(XOpenDisplay(NULL)), | |
| 227 root_window_(BadValue), | |
| 228 saved_auto_repeat_enabled_(false) { | |
| 229 } | |
| 230 | |
| 231 bool InputInjectorX11::Core::Init() { | |
| 232 CHECK(display_); | |
| 233 | |
| 234 if (!task_runner_->BelongsToCurrentThread()) | |
| 235 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::InitClipboard, this)); | |
| 236 | |
| 237 root_window_ = RootWindow(display_, DefaultScreen(display_)); | |
| 238 if (root_window_ == BadValue) { | |
| 239 LOG(ERROR) << "Unable to get the root window"; | |
| 240 return false; | |
| 241 } | |
| 242 | |
| 243 // TODO(ajwong): Do we want to check the major/minor version at all for XTest? | |
| 244 int major = 0; | |
| 245 int minor = 0; | |
| 246 if (!XTestQueryExtension(display_, &test_event_base_, &test_error_base_, | |
| 247 &major, &minor)) { | |
| 248 LOG(ERROR) << "Server does not support XTest."; | |
| 249 return false; | |
| 250 } | |
| 251 InitMouseButtonMap(); | |
| 252 return true; | |
| 253 } | |
| 254 | |
| 255 void InputInjectorX11::Core::InjectClipboardEvent( | |
| 256 const ClipboardEvent& event) { | |
| 257 if (!task_runner_->BelongsToCurrentThread()) { | |
| 258 task_runner_->PostTask( | |
| 259 FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event)); | |
| 260 return; | |
| 261 } | |
| 262 | |
| 263 // |clipboard_| will ignore unknown MIME-types, and verify the data's format. | |
| 264 clipboard_->InjectClipboardEvent(event); | |
| 265 } | |
| 266 | |
| 267 void InputInjectorX11::Core::InjectKeyEvent(const KeyEvent& event) { | |
| 268 // HostEventDispatcher should filter events missing the pressed field. | |
| 269 if (!event.has_pressed() || !event.has_usb_keycode()) | |
| 270 return; | |
| 271 | |
| 272 if (!task_runner_->BelongsToCurrentThread()) { | |
| 273 task_runner_->PostTask(FROM_HERE, | |
| 274 base::Bind(&Core::InjectKeyEvent, this, event)); | |
| 275 return; | |
| 276 } | |
| 277 | |
| 278 int keycode = | |
| 279 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode()); | |
| 280 | |
| 281 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() | |
| 282 << " to keycode: " << keycode << std::dec; | |
| 283 | |
| 284 // Ignore events which can't be mapped. | |
| 285 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode()) | |
| 286 return; | |
| 287 | |
| 288 if (event.pressed()) { | |
| 289 if (pressed_keys_.find(keycode) != pressed_keys_.end()) { | |
| 290 // Key is already held down, so lift the key up to ensure this repeated | |
| 291 // press takes effect. | |
| 292 XTestFakeKeyEvent(display_, keycode, False, CurrentTime); | |
| 293 } | |
| 294 | |
| 295 if (pressed_keys_.empty()) { | |
| 296 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat | |
| 297 // if network congestion delays the key-up event from the client. | |
| 298 saved_auto_repeat_enabled_ = IsAutoRepeatEnabled(); | |
| 299 if (saved_auto_repeat_enabled_) | |
| 300 SetAutoRepeatEnabled(false); | |
| 301 } | |
| 302 pressed_keys_.insert(keycode); | |
| 303 } else { | |
| 304 pressed_keys_.erase(keycode); | |
| 305 if (pressed_keys_.empty()) { | |
| 306 // Re-enable auto-repeat, if necessary, when all keys are released. | |
| 307 if (saved_auto_repeat_enabled_) | |
| 308 SetAutoRepeatEnabled(true); | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime); | |
| 313 XFlush(display_); | |
| 314 } | |
| 315 | |
| 316 void InputInjectorX11::Core::InjectTextEvent(const TextEvent& event) { | |
| 317 if (!task_runner_->BelongsToCurrentThread()) { | |
| 318 task_runner_->PostTask(FROM_HERE, | |
| 319 base::Bind(&Core::InjectTextEvent, this, event)); | |
| 320 return; | |
| 321 } | |
| 322 | |
| 323 const std::string text = event.text(); | |
| 324 for (int32 index = 0; index < static_cast<int32>(text.size()); ++index) { | |
| 325 uint32_t code_point; | |
| 326 if (!base::ReadUnicodeCharacter( | |
| 327 text.c_str(), text.size(), &index, &code_point)) { | |
| 328 continue; | |
| 329 } | |
| 330 | |
| 331 uint32_t keycode; | |
| 332 uint32_t modifiers; | |
| 333 if (!FindKeycodeForUnicode(display_, code_point, &keycode, &modifiers)) | |
| 334 continue; | |
| 335 | |
| 336 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, modifiers); | |
| 337 | |
| 338 XTestFakeKeyEvent(display_, keycode, True, CurrentTime); | |
| 339 XTestFakeKeyEvent(display_, keycode, False, CurrentTime); | |
| 340 | |
| 341 XkbLockModifiers(display_, XkbUseCoreKbd, modifiers, 0); | |
| 342 } | |
| 343 | |
| 344 XFlush(display_); | |
| 345 } | |
| 346 | |
| 347 InputInjectorX11::Core::~Core() { | |
| 348 CHECK(pressed_keys_.empty()); | |
| 349 } | |
| 350 | |
| 351 void InputInjectorX11::Core::InitClipboard() { | |
| 352 DCHECK(task_runner_->BelongsToCurrentThread()); | |
| 353 clipboard_ = Clipboard::Create(); | |
| 354 } | |
| 355 | |
| 356 bool InputInjectorX11::Core::IsAutoRepeatEnabled() { | |
| 357 XKeyboardState state; | |
| 358 if (!XGetKeyboardControl(display_, &state)) { | |
| 359 LOG(ERROR) << "Failed to get keyboard auto-repeat status, assuming ON."; | |
| 360 return true; | |
| 361 } | |
| 362 return state.global_auto_repeat == AutoRepeatModeOn; | |
| 363 } | |
| 364 | |
| 365 void InputInjectorX11::Core::SetAutoRepeatEnabled(bool mode) { | |
| 366 XKeyboardControl control; | |
| 367 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff; | |
| 368 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control); | |
| 369 } | |
| 370 | |
| 371 void InputInjectorX11::Core::InjectScrollWheelClicks(int button, int count) { | |
| 372 if (button < 0) { | |
| 373 LOG(WARNING) << "Ignoring unmapped scroll wheel button"; | |
| 374 return; | |
| 375 } | |
| 376 for (int i = 0; i < count; i++) { | |
| 377 // Generate a button-down and a button-up to simulate a wheel click. | |
| 378 XTestFakeButtonEvent(display_, button, true, CurrentTime); | |
| 379 XTestFakeButtonEvent(display_, button, false, CurrentTime); | |
| 380 } | |
| 381 } | |
| 382 | |
| 383 void InputInjectorX11::Core::InjectMouseEvent(const MouseEvent& event) { | |
| 384 if (!task_runner_->BelongsToCurrentThread()) { | |
| 385 task_runner_->PostTask(FROM_HERE, | |
| 386 base::Bind(&Core::InjectMouseEvent, this, event)); | |
| 387 return; | |
| 388 } | |
| 389 | |
| 390 if (event.has_delta_x() && | |
| 391 event.has_delta_y() && | |
| 392 (event.delta_x() != 0 || event.delta_y() != 0)) { | |
| 393 latest_mouse_position_.set(-1, -1); | |
| 394 VLOG(3) << "Moving mouse by " << event.delta_x() << "," << event.delta_y(); | |
| 395 XTestFakeRelativeMotionEvent(display_, | |
| 396 event.delta_x(), event.delta_y(), | |
| 397 CurrentTime); | |
| 398 | |
| 399 } else if (event.has_x() && event.has_y()) { | |
| 400 // Injecting a motion event immediately before a button release results in | |
| 401 // a MotionNotify even if the mouse position hasn't changed, which confuses | |
| 402 // apps which assume MotionNotify implies movement. See crbug.com/138075. | |
| 403 bool inject_motion = true; | |
| 404 webrtc::DesktopVector new_mouse_position( | |
| 405 webrtc::DesktopVector(event.x(), event.y())); | |
| 406 if (event.has_button() && event.has_button_down() && !event.button_down()) { | |
| 407 if (new_mouse_position.equals(latest_mouse_position_)) | |
| 408 inject_motion = false; | |
| 409 } | |
| 410 | |
| 411 if (inject_motion) { | |
| 412 latest_mouse_position_.set(std::max(0, new_mouse_position.x()), | |
| 413 std::max(0, new_mouse_position.y())); | |
| 414 | |
| 415 VLOG(3) << "Moving mouse to " << latest_mouse_position_.x() | |
| 416 << "," << latest_mouse_position_.y(); | |
| 417 XTestFakeMotionEvent(display_, DefaultScreen(display_), | |
| 418 latest_mouse_position_.x(), | |
| 419 latest_mouse_position_.y(), | |
| 420 CurrentTime); | |
| 421 } | |
| 422 } | |
| 423 | |
| 424 if (event.has_button() && event.has_button_down()) { | |
| 425 int button_number = MouseButtonToX11ButtonNumber(event.button()); | |
| 426 | |
| 427 if (button_number < 0) { | |
| 428 LOG(WARNING) << "Ignoring unknown button type: " << event.button(); | |
| 429 return; | |
| 430 } | |
| 431 | |
| 432 VLOG(3) << "Button " << event.button() | |
| 433 << " received, sending " | |
| 434 << (event.button_down() ? "down " : "up ") | |
| 435 << button_number; | |
| 436 XTestFakeButtonEvent(display_, button_number, event.button_down(), | |
| 437 CurrentTime); | |
| 438 } | |
| 439 | |
| 440 // Older client plugins always send scroll events in pixels, which | |
| 441 // must be accumulated host-side. Recent client plugins send both | |
| 442 // pixels and ticks with every scroll event, allowing the host to | |
| 443 // choose the best model on a per-platform basis. Since we can only | |
| 444 // inject ticks on Linux, use them if available. | |
| 445 int ticks_y = 0; | |
| 446 if (event.has_wheel_ticks_y()) { | |
| 447 ticks_y = event.wheel_ticks_y(); | |
| 448 } else if (event.has_wheel_delta_y()) { | |
| 449 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel; | |
| 450 ticks_y = static_cast<int>(wheel_ticks_y_); | |
| 451 wheel_ticks_y_ -= ticks_y; | |
| 452 } | |
| 453 if (ticks_y != 0) { | |
| 454 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y), | |
| 455 abs(ticks_y)); | |
| 456 } | |
| 457 | |
| 458 int ticks_x = 0; | |
| 459 if (event.has_wheel_ticks_x()) { | |
| 460 ticks_x = event.wheel_ticks_x(); | |
| 461 } else if (event.has_wheel_delta_x()) { | |
| 462 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel; | |
| 463 ticks_x = static_cast<int>(wheel_ticks_x_); | |
| 464 wheel_ticks_x_ -= ticks_x; | |
| 465 } | |
| 466 if (ticks_x != 0) { | |
| 467 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x), | |
| 468 abs(ticks_x)); | |
| 469 } | |
| 470 | |
| 471 XFlush(display_); | |
| 472 } | |
| 473 | |
| 474 void InputInjectorX11::Core::InitMouseButtonMap() { | |
| 475 // TODO(rmsousa): Run this on global/device mapping change events. | |
| 476 | |
| 477 // Do not touch global pointer mapping, since this may affect the local user. | |
| 478 // Instead, try to work around it by reversing the mapping. | |
| 479 // Note that if a user has a global mapping that completely disables a button | |
| 480 // (by assigning 0 to it), we won't be able to inject it. | |
| 481 int num_buttons = XGetPointerMapping(display_, NULL, 0); | |
| 482 scoped_ptr<unsigned char[]> pointer_mapping(new unsigned char[num_buttons]); | |
| 483 num_buttons = XGetPointerMapping(display_, pointer_mapping.get(), | |
| 484 num_buttons); | |
| 485 for (int i = 0; i < kNumPointerButtons; i++) { | |
| 486 pointer_button_map_[i] = -1; | |
| 487 } | |
| 488 for (int i = 0; i < num_buttons; i++) { | |
| 489 // Reverse the mapping. | |
| 490 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons) | |
| 491 pointer_button_map_[pointer_mapping[i] - 1] = i + 1; | |
| 492 } | |
| 493 for (int i = 0; i < kNumPointerButtons; i++) { | |
| 494 if (pointer_button_map_[i] == -1) | |
| 495 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1; | |
| 496 } | |
| 497 | |
| 498 int opcode, event, error; | |
| 499 if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) { | |
| 500 // If XInput is not available, we're done. But it would be very unusual to | |
| 501 // have a server that supports XTest but not XInput, so log it as an error. | |
| 502 LOG(ERROR) << "X Input extension not available: " << error; | |
| 503 return; | |
| 504 } | |
| 505 | |
| 506 // Make sure the XTEST XInput pointer device mapping is trivial. It should be | |
| 507 // safe to reset this mapping, as it won't affect the user's local devices. | |
| 508 // In fact, the reason why we do this is because an old gnome-settings-daemon | |
| 509 // may have mistakenly applied left-handed preferences to the XTEST device. | |
| 510 XID device_id = 0; | |
| 511 bool device_found = false; | |
| 512 int num_devices; | |
| 513 XDeviceInfo* devices; | |
| 514 devices = XListInputDevices(display_, &num_devices); | |
| 515 for (int i = 0; i < num_devices; i++) { | |
| 516 XDeviceInfo* device_info = &devices[i]; | |
| 517 if (device_info->use == IsXExtensionPointer && | |
| 518 strcmp(device_info->name, "Virtual core XTEST pointer") == 0) { | |
| 519 device_id = device_info->id; | |
| 520 device_found = true; | |
| 521 break; | |
| 522 } | |
| 523 } | |
| 524 XFreeDeviceList(devices); | |
| 525 | |
| 526 if (!device_found) { | |
| 527 HOST_LOG << "Cannot find XTest device."; | |
| 528 return; | |
| 529 } | |
| 530 | |
| 531 XDevice* device = XOpenDevice(display_, device_id); | |
| 532 if (!device) { | |
| 533 LOG(ERROR) << "Cannot open XTest device."; | |
| 534 return; | |
| 535 } | |
| 536 | |
| 537 int num_device_buttons = XGetDeviceButtonMapping(display_, device, NULL, 0); | |
| 538 scoped_ptr<unsigned char[]> button_mapping(new unsigned char[num_buttons]); | |
| 539 for (int i = 0; i < num_device_buttons; i++) { | |
| 540 button_mapping[i] = i + 1; | |
| 541 } | |
| 542 error = XSetDeviceButtonMapping(display_, device, button_mapping.get(), | |
| 543 num_device_buttons); | |
| 544 if (error != Success) | |
| 545 LOG(ERROR) << "Failed to set XTest device button mapping: " << error; | |
| 546 | |
| 547 XCloseDevice(display_, device); | |
| 548 } | |
| 549 | |
| 550 int InputInjectorX11::Core::MouseButtonToX11ButtonNumber( | |
| 551 MouseEvent::MouseButton button) { | |
| 552 switch (button) { | |
| 553 case MouseEvent::BUTTON_LEFT: | |
| 554 return pointer_button_map_[0]; | |
| 555 | |
| 556 case MouseEvent::BUTTON_RIGHT: | |
| 557 return pointer_button_map_[2]; | |
| 558 | |
| 559 case MouseEvent::BUTTON_MIDDLE: | |
| 560 return pointer_button_map_[1]; | |
| 561 | |
| 562 case MouseEvent::BUTTON_UNDEFINED: | |
| 563 default: | |
| 564 return -1; | |
| 565 } | |
| 566 } | |
| 567 | |
| 568 int InputInjectorX11::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) { | |
| 569 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]); | |
| 570 } | |
| 571 | |
| 572 int InputInjectorX11::Core::VerticalScrollWheelToX11ButtonNumber(int dy) { | |
| 573 // Positive y-values are wheel scroll-up events (button 4), negative y-values | |
| 574 // are wheel scroll-down events (button 5). | |
| 575 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]); | |
| 576 } | |
| 577 | |
| 578 void InputInjectorX11::Core::Start( | |
| 579 scoped_ptr<protocol::ClipboardStub> client_clipboard) { | |
| 580 if (!task_runner_->BelongsToCurrentThread()) { | |
| 581 task_runner_->PostTask( | |
| 582 FROM_HERE, | |
| 583 base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); | |
| 584 return; | |
| 585 } | |
| 586 | |
| 587 InitMouseButtonMap(); | |
| 588 | |
| 589 clipboard_->Start(client_clipboard.Pass()); | |
| 590 } | |
| 591 | |
| 592 void InputInjectorX11::Core::Stop() { | |
| 593 if (!task_runner_->BelongsToCurrentThread()) { | |
| 594 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); | |
| 595 return; | |
| 596 } | |
| 597 | |
| 598 clipboard_->Stop(); | |
| 599 } | |
| 600 | |
| 601 } // namespace | |
| 602 | |
| 603 scoped_ptr<InputInjector> InputInjector::Create( | |
| 604 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | |
| 605 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { | |
| 606 scoped_ptr<InputInjectorX11> injector( | |
| 607 new InputInjectorX11(main_task_runner)); | |
| 608 if (!injector->Init()) | |
| 609 return nullptr; | |
| 610 return injector.Pass(); | |
| 611 } | |
| 612 | |
| 613 } // namespace remoting | |
| OLD | NEW |