Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / events / platform / x11 / x11_hotplug_event_handler.cc
blob79895500cee2aa24d35352840fc4d0866c021cc0
1 // Copyright 2014 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.
5 #include "ui/events/platform/x11/x11_hotplug_event_handler.h"
7 #include <X11/Xatom.h>
8 #include <X11/extensions/XInput.h>
9 #include <X11/extensions/XInput2.h>
11 #include <algorithm>
12 #include <cmath>
13 #include <set>
14 #include <string>
15 #include <vector>
17 #include "base/bind.h"
18 #include "base/command_line.h"
19 #include "base/location.h"
20 #include "base/logging.h"
21 #include "base/process/launch.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/strings/string_util.h"
24 #include "base/sys_info.h"
25 #include "base/thread_task_runner_handle.h"
26 #include "base/threading/worker_pool.h"
27 #include "ui/events/devices/device_data_manager.h"
28 #include "ui/events/devices/device_hotplug_event_observer.h"
29 #include "ui/events/devices/device_util_linux.h"
30 #include "ui/events/devices/input_device.h"
31 #include "ui/events/devices/keyboard_device.h"
32 #include "ui/events/devices/touchscreen_device.h"
33 #include "ui/gfx/x/x11_types.h"
35 #ifndef XI_PROP_PRODUCT_ID
36 #define XI_PROP_PRODUCT_ID "Device Product ID"
37 #endif
39 namespace ui {
41 namespace {
43 // Names of all known internal devices that should not be considered as
44 // keyboards.
45 // TODO(rsadam@): Identify these devices using udev rules. (Crbug.com/420728.)
46 const char* kKnownInvalidKeyboardDeviceNames[] = {"Power Button",
47 "Sleep Button",
48 "Video Bus",
49 "gpio-keys.5",
50 "gpio-keys.12",
51 "ROCKCHIP-I2S Headset Jack"};
53 const char* kCachedAtomList[] = {
54 "Abs MT Position X",
55 "Abs MT Position Y",
56 XI_KEYBOARD,
57 XI_MOUSE,
58 XI_TOUCHPAD,
59 XI_TOUCHSCREEN,
60 XI_PROP_PRODUCT_ID,
61 NULL,
64 enum DeviceType {
65 DEVICE_TYPE_KEYBOARD,
66 DEVICE_TYPE_MOUSE,
67 DEVICE_TYPE_TOUCHPAD,
68 DEVICE_TYPE_TOUCHSCREEN,
69 DEVICE_TYPE_OTHER
72 typedef base::Callback<void(const std::vector<KeyboardDevice>&)>
73 KeyboardDeviceCallback;
75 typedef base::Callback<void(const std::vector<TouchscreenDevice>&)>
76 TouchscreenDeviceCallback;
78 typedef base::Callback<void(const std::vector<InputDevice>&)>
79 InputDeviceCallback;
81 // Used for updating the state on the UI thread once device information is
82 // parsed on helper threads.
83 struct UiCallbacks {
84 KeyboardDeviceCallback keyboard_callback;
85 TouchscreenDeviceCallback touchscreen_callback;
86 InputDeviceCallback mouse_callback;
87 InputDeviceCallback touchpad_callback;
88 base::Closure hotplug_finished_callback;
91 // Stores a copy of the XIValuatorClassInfo values so X11 device processing can
92 // happen on a worker thread. This is needed since X11 structs are not copyable.
93 struct ValuatorClassInfo {
94 ValuatorClassInfo(const XIValuatorClassInfo& info)
95 : label(info.label),
96 max(info.max),
97 min(info.min),
98 mode(info.mode),
99 number(info.number) {}
101 Atom label;
102 double max;
103 double min;
104 int mode;
105 int number;
108 // Stores a copy of the XITouchClassInfo values so X11 device processing can
109 // happen on a worker thread. This is needed since X11 structs are not copyable.
110 struct TouchClassInfo {
111 TouchClassInfo() : mode(0), num_touches(0) {}
113 explicit TouchClassInfo(const XITouchClassInfo& info)
114 : mode(info.mode), num_touches(info.num_touches) {}
116 int mode;
117 int num_touches;
120 struct DeviceInfo {
121 DeviceInfo(const XIDeviceInfo& device,
122 DeviceType type,
123 const base::FilePath& path,
124 uint16_t vendor,
125 uint16_t product)
126 : id(device.deviceid),
127 name(device.name),
128 vendor_id(vendor),
129 product_id(product),
130 use(device.use),
131 type(type),
132 path(path) {
133 for (int i = 0; i < device.num_classes; ++i) {
134 switch (device.classes[i]->type) {
135 case XIValuatorClass:
136 valuator_class_infos.push_back(ValuatorClassInfo(
137 *reinterpret_cast<XIValuatorClassInfo*>(device.classes[i])));
138 break;
139 case XITouchClass:
140 // A device can have at most one XITouchClassInfo. Ref:
141 // http://manpages.ubuntu.com/manpages/saucy/man3/XIQueryDevice.3.html
142 DCHECK(!touch_class_info.mode);
143 touch_class_info = TouchClassInfo(
144 *reinterpret_cast<XITouchClassInfo*>(device.classes[i]));
145 break;
146 default:
147 break;
152 // Unique device identifier.
153 int id;
155 // Internal device name.
156 std::string name;
158 // USB-style device identifiers.
159 uint16_t vendor_id;
160 uint16_t product_id;
162 // Device type (ie: XIMasterPointer)
163 int use;
165 // Specifies the type of the device.
166 DeviceType type;
168 // Path to the actual device (ie: /dev/input/eventXX)
169 base::FilePath path;
171 std::vector<ValuatorClassInfo> valuator_class_infos;
173 TouchClassInfo touch_class_info;
176 // X11 display cache used on worker threads. This is filled on the UI thread and
177 // passed in to the worker threads.
178 struct DisplayState {
179 Atom mt_position_x;
180 Atom mt_position_y;
183 // Returns true if |name| is the name of a known invalid keyboard device. Note,
184 // this may return false negatives.
185 bool IsKnownInvalidKeyboardDevice(const std::string& name) {
186 std::string trimmed(name);
187 base::TrimWhitespaceASCII(name, base::TRIM_TRAILING, &trimmed);
188 for (const char* device_name : kKnownInvalidKeyboardDeviceNames) {
189 if (trimmed == device_name)
190 return true;
192 return false;
195 // Returns true if |name| is the name of a known XTEST device. Note, this may
196 // return false negatives.
197 bool IsTestDevice(const std::string& name) {
198 return name.find("XTEST") != std::string::npos;
201 base::FilePath GetDevicePath(XDisplay* dpy, const XIDeviceInfo& device) {
202 // Skip the main pointer and keyboard since XOpenDevice() generates a
203 // BadDevice error when passed these devices.
204 if (device.use == XIMasterPointer || device.use == XIMasterKeyboard)
205 return base::FilePath();
207 // Input device has a property "Device Node" pointing to its dev input node,
208 // e.g. Device Node (250): "/dev/input/event8"
209 Atom device_node = XInternAtom(dpy, "Device Node", False);
210 if (device_node == None)
211 return base::FilePath();
213 Atom actual_type;
214 int actual_format;
215 unsigned long nitems, bytes_after;
216 unsigned char* data;
217 XDevice* dev = XOpenDevice(dpy, device.deviceid);
218 if (!dev)
219 return base::FilePath();
221 if (XGetDeviceProperty(dpy,
222 dev,
223 device_node,
225 1000,
226 False,
227 AnyPropertyType,
228 &actual_type,
229 &actual_format,
230 &nitems,
231 &bytes_after,
232 &data) != Success) {
233 XCloseDevice(dpy, dev);
234 return base::FilePath();
237 std::string path;
238 // Make sure the returned value is a string.
239 if (actual_type == XA_STRING && actual_format == 8)
240 path = reinterpret_cast<char*>(data);
242 XFree(data);
243 XCloseDevice(dpy, dev);
245 return base::FilePath(path);
248 // Helper used to parse keyboard information. When it is done it uses
249 // |reply_runner| and |callback| to update the state on the UI thread.
250 void HandleKeyboardDevicesInWorker(
251 const std::vector<DeviceInfo>& device_infos,
252 scoped_refptr<base::TaskRunner> reply_runner,
253 const KeyboardDeviceCallback& callback) {
254 std::vector<KeyboardDevice> devices;
256 for (const DeviceInfo& device_info : device_infos) {
257 if (device_info.type != DEVICE_TYPE_KEYBOARD)
258 continue;
259 if (device_info.use != XISlaveKeyboard)
260 continue; // Assume all keyboards are keyboard slaves
261 if (IsKnownInvalidKeyboardDevice(device_info.name))
262 continue; // Skip invalid devices.
263 InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
264 KeyboardDevice keyboard(device_info.id, type, device_info.name);
265 devices.push_back(keyboard);
268 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
271 // Helper used to parse mouse information. When it is done it uses
272 // |reply_runner| and |callback| to update the state on the UI thread.
273 void HandleMouseDevicesInWorker(const std::vector<DeviceInfo>& device_infos,
274 scoped_refptr<base::TaskRunner> reply_runner,
275 const InputDeviceCallback& callback) {
276 std::vector<InputDevice> devices;
277 for (const DeviceInfo& device_info : device_infos) {
278 if (device_info.type != DEVICE_TYPE_MOUSE ||
279 device_info.use != XISlavePointer) {
280 continue;
283 InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
284 devices.push_back(InputDevice(device_info.id, type, device_info.name));
287 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
290 // Helper used to parse touchpad information. When it is done it uses
291 // |reply_runner| and |callback| to update the state on the UI thread.
292 void HandleTouchpadDevicesInWorker(const std::vector<DeviceInfo>& device_infos,
293 scoped_refptr<base::TaskRunner> reply_runner,
294 const InputDeviceCallback& callback) {
295 std::vector<InputDevice> devices;
296 for (const DeviceInfo& device_info : device_infos) {
297 if (device_info.type != DEVICE_TYPE_TOUCHPAD ||
298 device_info.use != XISlavePointer) {
299 continue;
302 InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
303 devices.push_back(InputDevice(device_info.id, type, device_info.name));
306 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
309 // Helper used to parse touchscreen information. When it is done it uses
310 // |reply_runner| and |callback| to update the state on the UI thread.
311 void HandleTouchscreenDevicesInWorker(
312 const std::vector<DeviceInfo>& device_infos,
313 const DisplayState& display_state,
314 scoped_refptr<base::TaskRunner> reply_runner,
315 const TouchscreenDeviceCallback& callback) {
316 std::vector<TouchscreenDevice> devices;
317 if (display_state.mt_position_x == None ||
318 display_state.mt_position_y == None)
319 return;
321 for (const DeviceInfo& device_info : device_infos) {
322 if (device_info.type != DEVICE_TYPE_TOUCHSCREEN ||
323 (device_info.use != XIFloatingSlave &&
324 device_info.use != XISlavePointer)) {
325 continue;
328 // Touchscreens should be direct touch devices.
329 if (device_info.touch_class_info.mode != XIDirectTouch)
330 continue;
332 double max_x = -1.0;
333 double max_y = -1.0;
335 for (const ValuatorClassInfo& valuator : device_info.valuator_class_infos) {
336 if (display_state.mt_position_x == valuator.label) {
337 // Ignore X axis valuator with unexpected properties
338 if (valuator.number == 0 && valuator.mode == Absolute &&
339 valuator.min == 0.0) {
340 max_x = valuator.max;
342 } else if (display_state.mt_position_y == valuator.label) {
343 // Ignore Y axis valuator with unexpected properties
344 if (valuator.number == 1 && valuator.mode == Absolute &&
345 valuator.min == 0.0) {
346 max_y = valuator.max;
351 // Touchscreens should have absolute X and Y axes.
352 if (max_x > 0.0 && max_y > 0.0) {
353 InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
354 // |max_x| and |max_y| are inclusive values, so we need to add 1 to get
355 // the size.
356 devices.push_back(
357 TouchscreenDevice(device_info.id, type, device_info.name,
358 gfx::Size(max_x + 1, max_y + 1),
359 device_info.touch_class_info.num_touches));
363 reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
366 // Called on a worker thread to parse the device information.
367 void HandleHotplugEventInWorker(
368 const std::vector<DeviceInfo>& devices,
369 const DisplayState& display_state,
370 scoped_refptr<base::TaskRunner> reply_runner,
371 const UiCallbacks& callbacks) {
372 HandleTouchscreenDevicesInWorker(
373 devices, display_state, reply_runner, callbacks.touchscreen_callback);
374 HandleKeyboardDevicesInWorker(
375 devices, reply_runner, callbacks.keyboard_callback);
376 HandleMouseDevicesInWorker(devices, reply_runner, callbacks.mouse_callback);
377 HandleTouchpadDevicesInWorker(devices, reply_runner,
378 callbacks.touchpad_callback);
379 reply_runner->PostTask(FROM_HERE, callbacks.hotplug_finished_callback);
382 DeviceHotplugEventObserver* GetHotplugEventObserver() {
383 return DeviceDataManager::GetInstance();
386 void OnKeyboardDevices(const std::vector<KeyboardDevice>& devices) {
387 GetHotplugEventObserver()->OnKeyboardDevicesUpdated(devices);
390 void OnTouchscreenDevices(const std::vector<TouchscreenDevice>& devices) {
391 GetHotplugEventObserver()->OnTouchscreenDevicesUpdated(devices);
394 void OnMouseDevices(const std::vector<InputDevice>& devices) {
395 GetHotplugEventObserver()->OnMouseDevicesUpdated(devices);
398 void OnTouchpadDevices(const std::vector<InputDevice>& devices) {
399 GetHotplugEventObserver()->OnTouchpadDevicesUpdated(devices);
402 void OnHotplugFinished() {
403 GetHotplugEventObserver()->OnDeviceListsComplete();
406 } // namespace
408 X11HotplugEventHandler::X11HotplugEventHandler()
409 : atom_cache_(gfx::GetXDisplay(), kCachedAtomList) {
412 X11HotplugEventHandler::~X11HotplugEventHandler() {
415 void X11HotplugEventHandler::OnHotplugEvent() {
416 Display* display = gfx::GetXDisplay();
417 const XDeviceList& device_list_xi =
418 DeviceListCacheX11::GetInstance()->GetXDeviceList(display);
419 const XIDeviceList& device_list_xi2 =
420 DeviceListCacheX11::GetInstance()->GetXI2DeviceList(display);
422 const int kMaxDeviceNum = 128;
423 DeviceType device_types[kMaxDeviceNum];
424 for (int i = 0; i < kMaxDeviceNum; ++i)
425 device_types[i] = DEVICE_TYPE_OTHER;
427 for (int i = 0; i < device_list_xi.count; ++i) {
428 int id = device_list_xi[i].id;
429 if (id < 0 || id >= kMaxDeviceNum)
430 continue;
432 Atom type = device_list_xi[i].type;
433 if (type == atom_cache_.GetAtom(XI_KEYBOARD))
434 device_types[id] = DEVICE_TYPE_KEYBOARD;
435 else if (type == atom_cache_.GetAtom(XI_MOUSE))
436 device_types[id] = DEVICE_TYPE_MOUSE;
437 else if (type == atom_cache_.GetAtom(XI_TOUCHPAD))
438 device_types[id] = DEVICE_TYPE_TOUCHPAD;
439 else if (type == atom_cache_.GetAtom(XI_TOUCHSCREEN))
440 device_types[id] = DEVICE_TYPE_TOUCHSCREEN;
443 std::vector<DeviceInfo> device_infos;
444 for (int i = 0; i < device_list_xi2.count; ++i) {
445 const XIDeviceInfo& device = device_list_xi2[i];
446 if (!device.enabled || IsTestDevice(device.name))
447 continue;
449 DeviceType device_type =
450 (device.deviceid >= 0 && device.deviceid < kMaxDeviceNum)
451 ? device_types[device.deviceid]
452 : DEVICE_TYPE_OTHER;
454 // Obtain the USB-style vendor and product identifiers.
455 // (On Linux, XI2 makes this available for all evdev devices.
456 uint32_t* product_info;
457 Atom type;
458 int format_return;
459 unsigned long num_items_return;
460 unsigned long bytes_after_return;
461 uint16_t vendor = 0;
462 uint16_t product = 0;
463 if (XIGetProperty(gfx::GetXDisplay(), device.deviceid,
464 atom_cache_.GetAtom(XI_PROP_PRODUCT_ID), 0, 2, 0,
465 XA_INTEGER, &type, &format_return, &num_items_return,
466 &bytes_after_return,
467 reinterpret_cast<unsigned char**>(&product_info)) == 0 &&
468 product_info) {
469 if (num_items_return == 2) {
470 vendor = product_info[0];
471 product = product_info[1];
473 XFree(product_info);
476 device_infos.push_back(DeviceInfo(
477 device, device_type, GetDevicePath(display, device), vendor, product));
480 // X11 is not thread safe, so first get all the required state.
481 DisplayState display_state;
482 display_state.mt_position_x = atom_cache_.GetAtom("Abs MT Position X");
483 display_state.mt_position_y = atom_cache_.GetAtom("Abs MT Position Y");
485 UiCallbacks callbacks;
486 callbacks.keyboard_callback = base::Bind(&OnKeyboardDevices);
487 callbacks.touchscreen_callback = base::Bind(&OnTouchscreenDevices);
488 callbacks.mouse_callback = base::Bind(&OnMouseDevices);
489 callbacks.touchpad_callback = base::Bind(&OnTouchpadDevices);
490 callbacks.hotplug_finished_callback = base::Bind(&OnHotplugFinished);
492 // Parsing the device information may block, so delegate the operation to a
493 // worker thread. Once the device information is extracted the parsed devices
494 // will be returned via the callbacks.
495 base::WorkerPool::PostTask(
496 FROM_HERE,
497 base::Bind(&HandleHotplugEventInWorker, device_infos, display_state,
498 base::ThreadTaskRunnerHandle::Get(), callbacks),
499 true /* task_is_slow */);
502 } // namespace ui