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/event_executor.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 Source/WebKit/chromium/src/gtk/WebInputFactory.cc. | |
38 const float kWheelTicksPerPixel = 3.0f / 160.0f; | |
39 | |
40 // A class to generate events on Linux. | |
41 class EventExecutorLinux : public EventExecutor { | |
42 public: | |
43 explicit EventExecutorLinux( | |
44 scoped_refptr<base::SingleThreadTaskRunner> task_runner); | |
45 virtual ~EventExecutorLinux(); | |
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 // EventExecutor interface. | |
57 virtual void Start( | |
58 scoped_ptr<protocol::ClipboardStub> client_clipboard) OVERRIDE; | |
59 | |
60 private: | |
61 // The actual implementation resides in EventExecutorLinux::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 EventExecutor 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(EventExecutorLinux); | |
133 }; | |
134 | |
135 EventExecutorLinux::EventExecutorLinux( | |
136 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { | |
137 core_ = new Core(task_runner); | |
138 } | |
139 EventExecutorLinux::~EventExecutorLinux() { | |
140 core_->Stop(); | |
141 } | |
142 | |
143 bool EventExecutorLinux::Init() { | |
144 return core_->Init(); | |
145 } | |
146 | |
147 void EventExecutorLinux::InjectClipboardEvent(const ClipboardEvent& event) { | |
148 core_->InjectClipboardEvent(event); | |
149 } | |
150 | |
151 void EventExecutorLinux::InjectKeyEvent(const KeyEvent& event) { | |
152 core_->InjectKeyEvent(event); | |
153 } | |
154 | |
155 void EventExecutorLinux::InjectMouseEvent(const MouseEvent& event) { | |
156 core_->InjectMouseEvent(event); | |
157 } | |
158 | |
159 void EventExecutorLinux::Start( | |
160 scoped_ptr<protocol::ClipboardStub> client_clipboard) { | |
161 core_->Start(client_clipboard.Pass()); | |
162 } | |
163 | |
164 EventExecutorLinux::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 EventExecutorLinux::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 EventExecutorLinux::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 EventExecutorLinux::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 EventExecutorLinux::Core::~Core() { | |
260 CHECK(pressed_keys_.empty()); | |
261 } | |
262 | |
263 void EventExecutorLinux::Core::InitClipboard() { | |
264 DCHECK(task_runner_->BelongsToCurrentThread()); | |
265 clipboard_ = Clipboard::Create(); | |
266 } | |
267 | |
268 bool EventExecutorLinux::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 EventExecutorLinux::Core::SetAutoRepeatEnabled(bool mode) { | |
278 XKeyboardControl control; | |
279 control.auto_repeat_mode = mode ? AutoRepeatModeOn : AutoRepeatModeOff; | |
280 XChangeKeyboardControl(display_, KBAutoRepeatMode, &control); | |
281 } | |
282 | |
283 void EventExecutorLinux::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 EventExecutorLinux::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 int ticks_y = 0; | |
344 if (event.has_wheel_delta_y()) { | |
345 wheel_ticks_y_ += event.wheel_delta_y() * kWheelTicksPerPixel; | |
346 ticks_y = static_cast<int>(wheel_ticks_y_); | |
347 wheel_ticks_y_ -= ticks_y; | |
348 } | |
349 if (ticks_y != 0) { | |
350 InjectScrollWheelClicks(VerticalScrollWheelToX11ButtonNumber(ticks_y), | |
351 abs(ticks_y)); | |
352 } | |
353 | |
354 int ticks_x = 0; | |
355 if (event.has_wheel_delta_x()) { | |
356 wheel_ticks_x_ += event.wheel_delta_x() * kWheelTicksPerPixel; | |
357 ticks_x = static_cast<int>(wheel_ticks_x_); | |
358 wheel_ticks_x_ -= ticks_x; | |
359 } | |
360 if (ticks_x != 0) { | |
361 InjectScrollWheelClicks(HorizontalScrollWheelToX11ButtonNumber(ticks_x), | |
362 abs(ticks_x)); | |
363 } | |
364 | |
365 XFlush(display_); | |
366 } | |
367 | |
368 void EventExecutorLinux::Core::InitMouseButtonMap() { | |
369 // TODO(rmsousa): Run this on global/device mapping change events. | |
370 | |
371 // Do not touch global pointer mapping, since this may affect the local user. | |
372 // Instead, try to work around it by reversing the mapping. | |
373 // Note that if a user has a global mapping that completely disables a button | |
374 // (by assigning 0 to it), we won't be able to inject it. | |
375 int num_buttons = XGetPointerMapping(display_, NULL, 0); | |
376 scoped_array<unsigned char> pointer_mapping(new unsigned char[num_buttons]); | |
377 num_buttons = XGetPointerMapping(display_, pointer_mapping.get(), | |
378 num_buttons); | |
379 for (int i = 0; i < kNumPointerButtons; i++) { | |
380 pointer_button_map_[i] = -1; | |
381 } | |
382 for (int i = 0; i < num_buttons; i++) { | |
383 // Reverse the mapping. | |
384 if (pointer_mapping[i] > 0 && pointer_mapping[i] <= kNumPointerButtons) | |
385 pointer_button_map_[pointer_mapping[i] - 1] = i + 1; | |
386 } | |
387 for (int i = 0; i < kNumPointerButtons; i++) { | |
388 if (pointer_button_map_[i] == -1) | |
389 LOG(ERROR) << "Global pointer mapping does not support button " << i + 1; | |
390 } | |
391 | |
392 int opcode, event, error; | |
393 if (!XQueryExtension(display_, "XInputExtension", &opcode, &event, &error)) { | |
394 // If XInput is not available, we're done. But it would be very unusual to | |
395 // have a server that supports XTest but not XInput, so log it as an error. | |
396 LOG(ERROR) << "X Input extension not available: " << error; | |
397 return; | |
398 } | |
399 | |
400 // Make sure the XTEST XInput pointer device mapping is trivial. It should be | |
401 // safe to reset this mapping, as it won't affect the user's local devices. | |
402 // In fact, the reason why we do this is because an old gnome-settings-daemon | |
403 // may have mistakenly applied left-handed preferences to the XTEST device. | |
404 XID device_id = 0; | |
405 bool device_found = false; | |
406 int num_devices; | |
407 XDeviceInfo* devices; | |
408 devices = XListInputDevices(display_, &num_devices); | |
409 for (int i = 0; i < num_devices; i++) { | |
410 XDeviceInfo* device_info = &devices[i]; | |
411 if (device_info->use == IsXExtensionPointer && | |
412 strcmp(device_info->name, "Virtual core XTEST pointer") == 0) { | |
413 device_id = device_info->id; | |
414 device_found = true; | |
415 break; | |
416 } | |
417 } | |
418 XFreeDeviceList(devices); | |
419 | |
420 if (!device_found) { | |
421 LOG(INFO) << "Cannot find XTest device."; | |
422 return; | |
423 } | |
424 | |
425 XDevice* device = XOpenDevice(display_, device_id); | |
426 if (!device) { | |
427 LOG(ERROR) << "Cannot open XTest device."; | |
428 return; | |
429 } | |
430 | |
431 int num_device_buttons = XGetDeviceButtonMapping(display_, device, NULL, 0); | |
432 scoped_array<unsigned char> button_mapping(new unsigned char[num_buttons]); | |
433 for (int i = 0; i < num_device_buttons; i++) { | |
434 button_mapping[i] = i + 1; | |
435 } | |
436 error = XSetDeviceButtonMapping(display_, device, button_mapping.get(), | |
437 num_device_buttons); | |
438 if (error != Success) | |
439 LOG(ERROR) << "Failed to set XTest device button mapping: " << error; | |
440 | |
441 XCloseDevice(display_, device); | |
442 } | |
443 | |
444 int EventExecutorLinux::Core::MouseButtonToX11ButtonNumber( | |
445 MouseEvent::MouseButton button) { | |
446 switch (button) { | |
447 case MouseEvent::BUTTON_LEFT: | |
448 return pointer_button_map_[0]; | |
449 | |
450 case MouseEvent::BUTTON_RIGHT: | |
451 return pointer_button_map_[2]; | |
452 | |
453 case MouseEvent::BUTTON_MIDDLE: | |
454 return pointer_button_map_[1]; | |
455 | |
456 case MouseEvent::BUTTON_UNDEFINED: | |
457 default: | |
458 return -1; | |
459 } | |
460 } | |
461 | |
462 int EventExecutorLinux::Core::HorizontalScrollWheelToX11ButtonNumber(int dx) { | |
463 return (dx > 0 ? pointer_button_map_[5] : pointer_button_map_[6]); | |
464 } | |
465 | |
466 int EventExecutorLinux::Core::VerticalScrollWheelToX11ButtonNumber(int dy) { | |
467 // Positive y-values are wheel scroll-up events (button 4), negative y-values | |
468 // are wheel scroll-down events (button 5). | |
469 return (dy > 0 ? pointer_button_map_[3] : pointer_button_map_[4]); | |
470 } | |
471 | |
472 void EventExecutorLinux::Core::Start( | |
473 scoped_ptr<protocol::ClipboardStub> client_clipboard) { | |
474 if (!task_runner_->BelongsToCurrentThread()) { | |
475 task_runner_->PostTask( | |
476 FROM_HERE, | |
477 base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); | |
478 return; | |
479 } | |
480 | |
481 InitMouseButtonMap(); | |
482 | |
483 clipboard_->Start(client_clipboard.Pass()); | |
484 } | |
485 | |
486 void EventExecutorLinux::Core::Stop() { | |
487 if (!task_runner_->BelongsToCurrentThread()) { | |
488 task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); | |
489 return; | |
490 } | |
491 | |
492 clipboard_->Stop(); | |
493 } | |
494 | |
495 } // namespace | |
496 | |
497 scoped_ptr<EventExecutor> EventExecutor::Create( | |
498 scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, | |
499 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { | |
500 scoped_ptr<EventExecutorLinux> executor( | |
501 new EventExecutorLinux(main_task_runner)); | |
502 if (!executor->Init()) | |
503 return scoped_ptr<EventExecutor>(NULL); | |
504 return executor.PassAs<EventExecutor>(); | |
505 } | |
506 | |
507 } // namespace remoting | |
OLD | NEW |