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 <stddef.h>
8 #include <stdint.h>
9 #include <memory>
10 #include <set>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/compiler_specific.h"
15 #include "base/location.h"
16 #include "base/macros.h"
17 #include "base/optional.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/strings/utf_string_conversion_utils.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22 #include "remoting/base/logging.h"
23 #include "remoting/host/clipboard.h"
24 #include "remoting/host/linux/unicode_to_keysym.h"
25 #include "remoting/host/linux/x11_character_injector.h"
26 #include "remoting/host/linux/x11_keyboard_impl.h"
27 #include "remoting/host/linux/x11_util.h"
28 #include "remoting/proto/internal.pb.h"
29 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
30 #include "ui/events/keycodes/dom/dom_code.h"
31 #include "ui/events/keycodes/dom/keycode_converter.h"
32 #include "ui/gfx/x/keysyms/keysyms.h"
33 #include "ui/gfx/x/xinput.h"
34 #include "ui/gfx/x/xkb.h"
35 #include "ui/gfx/x/xproto.h"
36 #include "ui/gfx/x/xtest.h"
37
38 #if defined(OS_CHROMEOS)
39 #include "remoting/host/chromeos/point_transformer.h"
40 #endif
41
42 namespace remoting {
43
44 namespace {
45
46 using protocol::ClipboardEvent;
47 using protocol::KeyEvent;
48 using protocol::MouseEvent;
49 using protocol::TextEvent;
50 using protocol::TouchEvent;
51
52 enum class ScrollDirection {
53 DOWN = -1,
54 UP = 1,
55 NONE = 0,
56 };
57
WheelDeltaToScrollDirection(float num)58 ScrollDirection WheelDeltaToScrollDirection(float num) {
59 return (num > 0) ? ScrollDirection::UP
60 : (num < 0) ? ScrollDirection::DOWN
61 : ScrollDirection::NONE;
62 }
63
IsDomModifierKey(ui::DomCode dom_code)64 bool IsDomModifierKey(ui::DomCode dom_code) {
65 return dom_code == ui::DomCode::CONTROL_LEFT ||
66 dom_code == ui::DomCode::SHIFT_LEFT ||
67 dom_code == ui::DomCode::ALT_LEFT ||
68 dom_code == ui::DomCode::META_LEFT ||
69 dom_code == ui::DomCode::CONTROL_RIGHT ||
70 dom_code == ui::DomCode::SHIFT_RIGHT ||
71 dom_code == ui::DomCode::ALT_RIGHT ||
72 dom_code == ui::DomCode::META_RIGHT;
73 }
74
75 // Pixel-to-wheel-ticks conversion ratio used by GTK.
76 // From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp .
77 const float kWheelTicksPerPixel = 3.0f / 160.0f;
78
79 // When the user is scrolling, generate at least one tick per time period.
80 const base::TimeDelta kContinuousScrollTimeout =
81 base::TimeDelta::FromMilliseconds(500);
82
83 // A class to generate events on X11.
84 class InputInjectorX11 : public InputInjector {
85 public:
86 explicit InputInjectorX11(
87 scoped_refptr<base::SingleThreadTaskRunner> task_runner);
88 ~InputInjectorX11() override;
89
90 bool Init();
91
92 // Clipboard stub interface.
93 void InjectClipboardEvent(const ClipboardEvent& event) override;
94
95 // InputStub interface.
96 void InjectKeyEvent(const KeyEvent& event) override;
97 void InjectTextEvent(const TextEvent& event) override;
98 void InjectMouseEvent(const MouseEvent& event) override;
99 void InjectTouchEvent(const TouchEvent& event) override;
100
101 // InputInjector interface.
102 void Start(
103 std::unique_ptr<protocol::ClipboardStub> client_clipboard) override;
104
105 private:
106 // The actual implementation resides in InputInjectorX11::Core class.
107 class Core : public base::RefCountedThreadSafe<Core> {
108 public:
109 explicit Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner);
110
111 bool Init();
112
113 // Mirrors the ClipboardStub interface.
114 void InjectClipboardEvent(const ClipboardEvent& event);
115
116 // Mirrors the InputStub interface.
117 void InjectKeyEvent(const KeyEvent& event);
118 void InjectTextEvent(const TextEvent& event);
119 void InjectMouseEvent(const MouseEvent& event);
120
121 // Mirrors the InputInjector interface.
122 void Start(std::unique_ptr<protocol::ClipboardStub> client_clipboard);
123
124 void Stop();
125
126 private:
127 friend class base::RefCountedThreadSafe<Core>;
128 virtual ~Core();
129
130 void InitClipboard();
131
132 // Queries whether keyboard auto-repeat is globally enabled. This is used
133 // to decide whether to temporarily disable then restore this setting. If
134 // auto-repeat has already been disabled, this class should leave it
135 // untouched.
136 bool IsAutoRepeatEnabled();
137
138 // Enables or disables keyboard auto-repeat globally.
139 void SetAutoRepeatEnabled(bool enabled);
140
141 // Check if the given scan code is caps lock or num lock.
142 bool IsLockKey(x11::KeyCode keycode);
143
144 // Sets the keyboard lock states to those provided.
145 void SetLockStates(base::Optional<bool> caps_lock,
146 base::Optional<bool> num_lock);
147
148 void InjectScrollWheelClicks(int button, int count);
149 // Compensates for global button mappings and resets the XTest device
150 // mapping.
151 void InitMouseButtonMap();
152 int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button);
153 int HorizontalScrollWheelToX11ButtonNumber(int dx);
154 int VerticalScrollWheelToX11ButtonNumber(int dy);
155
156 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
157
158 std::set<int> pressed_keys_;
159 webrtc::DesktopVector latest_mouse_position_ =
160 webrtc::DesktopVector(-1, -1);
161 float wheel_ticks_x_ = 0;
162 float wheel_ticks_y_ = 0;
163 base::Time latest_tick_y_event_;
164 // The direction of the last scroll event that resulted in at least one
165 // "tick" being injected.
166 ScrollDirection latest_tick_y_direction_ = ScrollDirection::NONE;
167
168 // X11 graphics context.
169 x11::Connection connection_;
170
171 // Number of buttons we support.
172 // Left, Right, Middle, VScroll Up/Down, HScroll Left/Right, back, forward.
173 static const int kNumPointerButtons = 9;
174
175 int pointer_button_map_[kNumPointerButtons];
176
177 #if defined(OS_CHROMEOS)
178 PointTransformer point_transformer_;
179 #endif
180
181 std::unique_ptr<Clipboard> clipboard_;
182
183 std::unique_ptr<X11CharacterInjector> character_injector_;
184
185 bool saved_auto_repeat_enabled_ = false;
186
187 DISALLOW_COPY_AND_ASSIGN(Core);
188 };
189
190 scoped_refptr<Core> core_;
191
192 DISALLOW_COPY_AND_ASSIGN(InputInjectorX11);
193 };
194
InputInjectorX11(scoped_refptr<base::SingleThreadTaskRunner> task_runner)195 InputInjectorX11::InputInjectorX11(
196 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
197 core_ = new Core(task_runner);
198 }
199
~InputInjectorX11()200 InputInjectorX11::~InputInjectorX11() {
201 core_->Stop();
202 }
203
Init()204 bool InputInjectorX11::Init() {
205 return core_->Init();
206 }
207
InjectClipboardEvent(const ClipboardEvent & event)208 void InputInjectorX11::InjectClipboardEvent(const ClipboardEvent& event) {
209 core_->InjectClipboardEvent(event);
210 }
211
InjectKeyEvent(const KeyEvent & event)212 void InputInjectorX11::InjectKeyEvent(const KeyEvent& event) {
213 core_->InjectKeyEvent(event);
214 }
215
InjectTextEvent(const TextEvent & event)216 void InputInjectorX11::InjectTextEvent(const TextEvent& event) {
217 core_->InjectTextEvent(event);
218 }
219
InjectMouseEvent(const MouseEvent & event)220 void InputInjectorX11::InjectMouseEvent(const MouseEvent& event) {
221 core_->InjectMouseEvent(event);
222 }
223
InjectTouchEvent(const TouchEvent & event)224 void InputInjectorX11::InjectTouchEvent(const TouchEvent& event) {
225 NOTIMPLEMENTED() << "Raw touch event injection not implemented for X11.";
226 }
227
Start(std::unique_ptr<protocol::ClipboardStub> client_clipboard)228 void InputInjectorX11::Start(
229 std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
230 core_->Start(std::move(client_clipboard));
231 }
232
Core(scoped_refptr<base::SingleThreadTaskRunner> task_runner)233 InputInjectorX11::Core::Core(
234 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
235 : task_runner_(task_runner) {}
236
Init()237 bool InputInjectorX11::Core::Init() {
238 CHECK(connection_.Ready());
239
240 if (!task_runner_->BelongsToCurrentThread())
241 task_runner_->PostTask(FROM_HERE,
242 base::BindOnce(&Core::InitClipboard, this));
243
244 if (!IgnoreXServerGrabs(&connection_, true)) {
245 LOG(ERROR) << "Server does not support XTest.";
246 return false;
247 }
248 InitMouseButtonMap();
249 return true;
250 }
251
InjectClipboardEvent(const ClipboardEvent & event)252 void InputInjectorX11::Core::InjectClipboardEvent(const ClipboardEvent& event) {
253 if (!task_runner_->BelongsToCurrentThread()) {
254 task_runner_->PostTask(
255 FROM_HERE, base::BindOnce(&Core::InjectClipboardEvent, this, event));
256 return;
257 }
258
259 // |clipboard_| will ignore unknown MIME-types, and verify the data's format.
260 clipboard_->InjectClipboardEvent(event);
261 }
262
InjectKeyEvent(const KeyEvent & event)263 void InputInjectorX11::Core::InjectKeyEvent(const KeyEvent& event) {
264 // HostEventDispatcher should filter events missing the pressed field.
265 if (!event.has_pressed() || !event.has_usb_keycode())
266 return;
267
268 if (!task_runner_->BelongsToCurrentThread()) {
269 task_runner_->PostTask(FROM_HERE,
270 base::BindOnce(&Core::InjectKeyEvent, this, event));
271 return;
272 }
273
274 int keycode =
275 ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode());
276
277 VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode()
278 << " to keycode: " << keycode << std::dec;
279
280 // Ignore events which can't be mapped.
281 if (keycode == ui::KeycodeConverter::InvalidNativeKeycode())
282 return;
283
284 if (event.pressed()) {
285 if (pressed_keys_.find(keycode) != pressed_keys_.end()) {
286 // Ignore repeats for modifier keys.
287 if (IsDomModifierKey(static_cast<ui::DomCode>(event.usb_keycode())))
288 return;
289 // Key is already held down, so lift the key up to ensure this repeated
290 // press takes effect.
291 connection_.xtest().FakeInput({x11::KeyEvent::Release, keycode});
292 }
293
294 if (!IsLockKey(static_cast<x11::KeyCode>(keycode))) {
295 base::Optional<bool> caps_lock;
296 base::Optional<bool> num_lock;
297
298 // For caps lock, check both the new caps_lock field and the old
299 // lock_states field.
300 if (event.has_caps_lock_state()) {
301 caps_lock = event.caps_lock_state();
302 } else if (event.has_lock_states()) {
303 caps_lock = (event.lock_states() &
304 protocol::KeyEvent::LOCK_STATES_CAPSLOCK) != 0;
305 }
306
307 // Not all clients have a concept of num lock. Since there's no way to
308 // distinguish these clients using the legacy lock_states field, only
309 // update if the new num_lock field is specified.
310 if (event.has_num_lock_state()) {
311 num_lock = event.num_lock_state();
312 }
313
314 SetLockStates(caps_lock, num_lock);
315 }
316
317 // We turn autorepeat off when we initially connect, but in can get
318 // re-enabled when, e.g., the desktop environment reapplies its settings.
319 if (IsAutoRepeatEnabled()) {
320 SetAutoRepeatEnabled(false);
321 }
322
323 pressed_keys_.insert(keycode);
324 } else {
325 pressed_keys_.erase(keycode);
326 }
327
328 auto opcode = event.pressed() ? x11::KeyEvent::Press : x11::KeyEvent::Release;
329 connection_.xtest().FakeInput({opcode, keycode});
330 connection_.Flush();
331 }
332
InjectTextEvent(const TextEvent & event)333 void InputInjectorX11::Core::InjectTextEvent(const TextEvent& event) {
334 if (!task_runner_->BelongsToCurrentThread()) {
335 task_runner_->PostTask(FROM_HERE,
336 base::BindOnce(&Core::InjectTextEvent, this, event));
337 return;
338 }
339
340 // Release all keys before injecting text event. This is necessary to avoid
341 // any interference with the currently pressed keys. E.g. if Shift is pressed
342 // when TextEvent is received.
343 for (int key : pressed_keys_)
344 connection_.xtest().FakeInput({x11::KeyEvent::Release, key});
345 pressed_keys_.clear();
346
347 const std::string text = event.text();
348 for (int32_t index = 0; index < static_cast<int32_t>(text.size()); ++index) {
349 uint32_t code_point;
350 if (!base::ReadUnicodeCharacter(text.c_str(), text.size(), &index,
351 &code_point)) {
352 continue;
353 }
354 character_injector_->Inject(code_point);
355 }
356 }
357
~Core()358 InputInjectorX11::Core::~Core() {
359 CHECK(pressed_keys_.empty());
360 }
361
InitClipboard()362 void InputInjectorX11::Core::InitClipboard() {
363 DCHECK(task_runner_->BelongsToCurrentThread());
364 clipboard_ = Clipboard::Create();
365 }
366
IsAutoRepeatEnabled()367 bool InputInjectorX11::Core::IsAutoRepeatEnabled() {
368 if (auto reply = connection_.GetKeyboardControl({}).Sync())
369 return reply->global_auto_repeat == x11::AutoRepeatMode::On;
370 LOG(ERROR) << "Failed to get keyboard auto-repeat status, assuming ON.";
371 return true;
372 }
373
SetAutoRepeatEnabled(bool mode)374 void InputInjectorX11::Core::SetAutoRepeatEnabled(bool mode) {
375 connection_.ChangeKeyboardControl(
376 {.auto_repeat_mode =
377 mode ? x11::AutoRepeatMode::On : x11::AutoRepeatMode::Off});
378 connection_.Flush();
379 }
380
IsLockKey(x11::KeyCode keycode)381 bool InputInjectorX11::Core::IsLockKey(x11::KeyCode keycode) {
382 auto state = connection_.xkb().GetState({}).Sync();
383 if (!state)
384 return false;
385 auto mods = state->baseMods | state->latchedMods | state->lockedMods;
386 auto keysym =
387 connection_.KeycodeToKeysym(keycode, static_cast<unsigned>(mods));
388 if (state && keysym)
389 return keysym == XK_Caps_Lock || keysym == XK_Num_Lock;
390 else
391 return false;
392 }
393
SetLockStates(base::Optional<bool> caps_lock,base::Optional<bool> num_lock)394 void InputInjectorX11::Core::SetLockStates(base::Optional<bool> caps_lock,
395 base::Optional<bool> num_lock) {
396 // The lock bits associated with each lock key.
397 auto caps_lock_mask = static_cast<unsigned int>(x11::ModMask::Lock);
398 auto num_lock_mask = static_cast<unsigned int>(x11::ModMask::c_2);
399
400 unsigned int update_mask = 0; // The lock bits we want to update
401 unsigned int lock_values = 0; // The value of those bits
402
403 if (caps_lock) {
404 update_mask |= caps_lock_mask;
405 if (*caps_lock) {
406 lock_values |= caps_lock_mask;
407 }
408 }
409
410 if (num_lock) {
411 update_mask |= num_lock_mask;
412 if (*num_lock) {
413 lock_values |= num_lock_mask;
414 }
415 }
416
417 if (update_mask) {
418 connection_.xkb().LatchLockState(
419 {static_cast<x11::Xkb::DeviceSpec>(x11::Xkb::Id::UseCoreKbd),
420 static_cast<x11::ModMask>(update_mask),
421 static_cast<x11::ModMask>(lock_values)});
422 }
423 }
424
InjectScrollWheelClicks(int button,int count)425 void InputInjectorX11::Core::InjectScrollWheelClicks(int button, int count) {
426 if (button < 0) {
427 LOG(WARNING) << "Ignoring unmapped scroll wheel button";
428 return;
429 }
430 for (int i = 0; i < count; i++) {
431 // Generate a button-down and a button-up to simulate a wheel click.
432 connection_.xtest().FakeInput({x11::ButtonEvent::Press, button});
433 connection_.xtest().FakeInput({x11::ButtonEvent::Release, button});
434 }
435 }
436
InjectMouseEvent(const MouseEvent & event)437 void InputInjectorX11::Core::InjectMouseEvent(const MouseEvent& event) {
438 if (!task_runner_->BelongsToCurrentThread()) {
439 task_runner_->PostTask(
440 FROM_HERE, base::BindOnce(&Core::InjectMouseEvent, this, event));
441 return;
442 }
443
444 if (event.has_delta_x() && event.has_delta_y() &&
445 (event.delta_x() != 0 || event.delta_y() != 0)) {
446 latest_mouse_position_.set(-1, -1);
447 VLOG(3) << "Moving mouse by " << event.delta_x() << "," << event.delta_y();
448 connection_.xtest().FakeInput({
449 .type = x11::MotionNotifyEvent::opcode,
450 .detail = true,
451 .rootX = event.delta_x(),
452 .rootY = event.delta_y(),
453 });
454 } else if (event.has_x() && event.has_y()) {
455 // Injecting a motion event immediately before a button release results in
456 // a MotionNotify even if the mouse position hasn't changed, which confuses
457 // apps which assume MotionNotify implies movement. See crbug.com/138075.
458 bool inject_motion = true;
459 webrtc::DesktopVector new_mouse_position(event.x(), event.y());
460 #if defined(OS_CHROMEOS)
461 // Interim hack to handle display rotation on Chrome OS.
462 // TODO(kelvin): Remove this when Chrome OS has completely migrated to
463 // Ozone (crbug.com/439287).
464 gfx::PointF screen_location = point_transformer_.ToScreenCoordinates(
465 gfx::PointF(event.x(), event.y()));
466 new_mouse_position.set(screen_location.x(), screen_location.y());
467 #endif
468 if (event.has_button() && event.has_button_down() && !event.button_down()) {
469 if (new_mouse_position.equals(latest_mouse_position_))
470 inject_motion = false;
471 }
472
473 if (inject_motion) {
474 latest_mouse_position_.set(std::max(0, new_mouse_position.x()),
475 std::max(0, new_mouse_position.y()));
476
477 VLOG(3) << "Moving mouse to " << latest_mouse_position_.x() << ","
478 << latest_mouse_position_.y();
479 connection_.xtest().FakeInput({
480 .type = x11::MotionNotifyEvent::opcode,
481 .detail = false,
482 .root = connection_.default_root(),
483 .rootX = latest_mouse_position_.x(),
484 .rootY = latest_mouse_position_.y(),
485 });
486 }
487 }
488
489 if (event.has_button() && event.has_button_down()) {
490 int button_number = MouseButtonToX11ButtonNumber(event.button());
491
492 if (button_number < 0) {
493 LOG(WARNING) << "Ignoring unknown button type: " << event.button();
494 return;
495 }
496
497 VLOG(3) << "Button " << event.button() << " received, sending "
498 << (event.button_down() ? "down " : "up ") << button_number;
499 auto opcode = event.button_down() ? x11::ButtonEvent::Press
500 : x11::ButtonEvent::Release;
501 connection_.xtest().FakeInput({opcode, button_number});
502 }
503
504 // remotedesktop.google.com currently sends scroll events in pixels, which
505 // are accumulated host-side.
506 int ticks_y = 0;
507 if (event.has_wheel_ticks_y()) {
508 ticks_y = event.wheel_ticks_y();
509 } else if (event.has_wheel_delta_y()) {
510 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel;
511 ticks_y = static_cast<int>(wheel_ticks_y_);
512 wheel_ticks_y_ -= ticks_y;
513 }
514 if (ticks_y == 0 && event.has_wheel_delta_y()) {
515 // For the y-direction only (the common case), try to ensure that a tick is
516 // injected when the user would expect one, regardless of how many pixels
517 // the client sends per tick (even if it accelerates wheel events). To do
518 // this, generate a tick if one has not occurred recently in the current
519 // scroll direction. The accumulated pixels are not reset in this case.
520 //
521 // The effect when a physical mouse is used is as follows:
522 //
523 // Client sends slightly too few pixels per tick (e.g. Linux):
524 // * First scroll in either direction synthesizes a tick.
525 // * Subsequent scrolls in the same direction are unaffected (their
526 // accumulated pixel deltas mostly meet the threshold for a regular
527 // tick; occasionally a tick will be dropped if the user is scrolling
528 // quickly).
529 //
530 // Client sends far too few pixels per tick, but applies acceleration
531 // (e.g. macOs, ChromeOS):
532 // * First scroll in either direction synthesizes a tick.
533 // * Slow scrolling will synthesize a tick a few times per second.
534 // * Fast scrolling is unaffected (acceleration means that enough pixels
535 // are accumulated naturally).
536 //
537 // Client sends too many pixels per tick (e.g. Windows):
538 // * Scrolling is unaffected (most scroll events generate one tick; every
539 // so often one generates two ticks).
540 //
541 // The effect when a trackpad is used is that the first tick in either
542 // direction occurs sooner; subsequent ticks are mostly unaffected.
543 const ScrollDirection current_tick_y_direction =
544 WheelDeltaToScrollDirection(event.wheel_delta_y());
545 if ((base::Time::Now() - latest_tick_y_event_ > kContinuousScrollTimeout) ||
546 latest_tick_y_direction_ != current_tick_y_direction) {
547 ticks_y = static_cast<int>(current_tick_y_direction);
548 }
549 }
550 if (ticks_y != 0) {
551 latest_tick_y_direction_ = WheelDeltaToScrollDirection(ticks_y);
552 latest_tick_y_event_ = base::Time::Now();
553 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y),
554 abs(ticks_y));
555 }
556
557 int ticks_x = 0;
558 if (event.has_wheel_ticks_x()) {
559 ticks_x = event.wheel_ticks_x();
560 } else if (event.has_wheel_delta_x()) {
561 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel;
562 ticks_x = static_cast<int>(wheel_ticks_x_);
563 wheel_ticks_x_ -= ticks_x;
564 }
565 if (ticks_x != 0) {
566 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x),
567 abs(ticks_x));
568 }
569
570 connection_.Flush();
571 }
572
InitMouseButtonMap()573 void InputInjectorX11::Core::InitMouseButtonMap() {
574 // TODO(rmsousa): Run this on global/device mapping change events.
575
576 // Do not touch global pointer mapping, since this may affect the local user.
577 // Instead, try to work around it by reversing the mapping.
578 // Note that if a user has a global mapping that completely disables a button
579 // (by assigning 0 to it), we won't be able to inject it.
580 std::vector<uint8_t> pointer_mapping;
581 if (auto reply = connection_.GetPointerMapping({}).Sync())
582 pointer_mapping = std::move(reply->map);
583 for (int& i : pointer_button_map_)
584 i = -1;
585 for (size_t i = 0; i < pointer_mapping.size(); i++) {
586 // Reverse the mapping.
587 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons)
588 pointer_button_map_[pointer_mapping[i] - 1] = i + 1;
589 }
590 for (int i = 0; i < kNumPointerButtons; i++) {
591 if (pointer_button_map_[i] == -1)
592 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1;
593 }
594
595 if (!connection_.QueryExtension({"XInputExtension"}).Sync()) {
596 // If XInput is not available, we're done. But it would be very unusual to
597 // have a server that supports XTest but not XInput, so log it as an error.
598 LOG(ERROR) << "X Input extension not available";
599 return;
600 }
601
602 // Make sure the XTEST XInput pointer device mapping is trivial. It should be
603 // safe to reset this mapping, as it won't affect the user's local devices.
604 // In fact, the reason why we do this is because an old gnome-settings-daemon
605 // may have mistakenly applied left-handed preferences to the XTEST device.
606 uint8_t device_id = 0;
607 bool device_found = false;
608 if (auto devices = connection_.xinput().ListInputDevices({}).Sync()) {
609 for (size_t i = 0; i < devices->devices.size(); i++) {
610 const auto& device_info = devices->devices[i];
611 const std::string& name = devices->names[i].name;
612 if (device_info.device_use ==
613 x11::Input::DeviceUse::IsXExtensionPointer &&
614 name == "Virtual core XTEST pointer") {
615 device_id = device_info.device_id;
616 device_found = true;
617 break;
618 }
619 }
620 }
621
622 if (!device_found) {
623 HOST_LOG << "Cannot find XTest device.";
624 return;
625 }
626
627 auto device = connection_.xinput().OpenDevice({device_id}).Sync();
628 if (!device) {
629 LOG(ERROR) << "Cannot open XTest device.";
630 return;
631 }
632
633 if (auto mapping =
634 connection_.xinput().GetDeviceButtonMapping({device_id}).Sync()) {
635 size_t num_device_buttons = mapping->map.size();
636 std::vector<uint8_t> new_mapping;
637 for (size_t i = 0; i < num_device_buttons; i++)
638 new_mapping.push_back(i + 1);
639 if (!connection_.xinput()
640 .SetDeviceButtonMapping({device_id, new_mapping})
641 .Sync()) {
642 LOG(ERROR) << "Failed to set XTest device button mapping";
643 }
644 }
645
646 connection_.xinput().CloseDevice({device_id});
647 connection_.Flush();
648 }
649
MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button)650 int InputInjectorX11::Core::MouseButtonToX11ButtonNumber(
651 MouseEvent::MouseButton button) {
652 switch (button) {
653 case MouseEvent::BUTTON_LEFT:
654 return pointer_button_map_[0];
655 case MouseEvent::BUTTON_RIGHT:
656 return pointer_button_map_[2];
657 case MouseEvent::BUTTON_MIDDLE:
658 return pointer_button_map_[1];
659 case MouseEvent::BUTTON_BACK:
660 return pointer_button_map_[7];
661 case MouseEvent::BUTTON_FORWARD:
662 return pointer_button_map_[8];
663 case MouseEvent::BUTTON_UNDEFINED:
664 default:
665 return -1;
666 }
667 }
668
HorizontalScrollWheelToX11ButtonNumber(int dx)669 int InputInjectorX11::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) {
670 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]);
671 }
672
VerticalScrollWheelToX11ButtonNumber(int dy)673 int InputInjectorX11::Core::VerticalScrollWheelToX11ButtonNumber(int dy) {
674 // Positive y-values are wheel scroll-up events (button 4), negative y-values
675 // are wheel scroll-down events (button 5).
676 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]);
677 }
678
Start(std::unique_ptr<protocol::ClipboardStub> client_clipboard)679 void InputInjectorX11::Core::Start(
680 std::unique_ptr<protocol::ClipboardStub> client_clipboard) {
681 if (!task_runner_->BelongsToCurrentThread()) {
682 task_runner_->PostTask(
683 FROM_HERE,
684 base::BindOnce(&Core::Start, this, std::move(client_clipboard)));
685 return;
686 }
687
688 InitMouseButtonMap();
689
690 clipboard_->Start(std::move(client_clipboard));
691
692 character_injector_ = std::make_unique<X11CharacterInjector>(
693 std::make_unique<X11KeyboardImpl>(&connection_));
694
695 // Disable auto-repeat, if necessary, to avoid triggering auto-repeat
696 // if network congestion delays the key-up event from the client. This is
697 // done for the duration of the session because some window managers do
698 // not handle changes to this setting efficiently.
699 saved_auto_repeat_enabled_ = IsAutoRepeatEnabled();
700 if (saved_auto_repeat_enabled_)
701 SetAutoRepeatEnabled(false);
702 }
703
Stop()704 void InputInjectorX11::Core::Stop() {
705 if (!task_runner_->BelongsToCurrentThread()) {
706 task_runner_->PostTask(FROM_HERE, base::BindOnce(&Core::Stop, this));
707 return;
708 }
709
710 clipboard_.reset();
711 character_injector_.reset();
712 // Re-enable auto-repeat, if necessary, on disconnect.
713 if (saved_auto_repeat_enabled_)
714 SetAutoRepeatEnabled(true);
715 }
716
717 } // namespace
718
719 // static
Create(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)720 std::unique_ptr<InputInjector> InputInjector::Create(
721 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
722 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) {
723 std::unique_ptr<InputInjectorX11> injector(
724 new InputInjectorX11(main_task_runner));
725 if (!injector->Init())
726 return nullptr;
727 return std::move(injector);
728 }
729
730 // static
SupportsTouchEvents()731 bool InputInjector::SupportsTouchEvents() {
732 return false;
733 }
734
735 } // namespace remoting
736