| Index: ui/events/platform/x11/x11_hotplug_event_handler.cc
|
| diff --git a/ui/events/platform/x11/x11_hotplug_event_handler.cc b/ui/events/platform/x11/x11_hotplug_event_handler.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..79895500cee2aa24d35352840fc4d0866c021cc0
|
| --- /dev/null
|
| +++ b/ui/events/platform/x11/x11_hotplug_event_handler.cc
|
| @@ -0,0 +1,502 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "ui/events/platform/x11/x11_hotplug_event_handler.h"
|
| +
|
| +#include <X11/Xatom.h>
|
| +#include <X11/extensions/XInput.h>
|
| +#include <X11/extensions/XInput2.h>
|
| +
|
| +#include <algorithm>
|
| +#include <cmath>
|
| +#include <set>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/command_line.h"
|
| +#include "base/location.h"
|
| +#include "base/logging.h"
|
| +#include "base/process/launch.h"
|
| +#include "base/single_thread_task_runner.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "base/sys_info.h"
|
| +#include "base/thread_task_runner_handle.h"
|
| +#include "base/threading/worker_pool.h"
|
| +#include "ui/events/devices/device_data_manager.h"
|
| +#include "ui/events/devices/device_hotplug_event_observer.h"
|
| +#include "ui/events/devices/device_util_linux.h"
|
| +#include "ui/events/devices/input_device.h"
|
| +#include "ui/events/devices/keyboard_device.h"
|
| +#include "ui/events/devices/touchscreen_device.h"
|
| +#include "ui/gfx/x/x11_types.h"
|
| +
|
| +#ifndef XI_PROP_PRODUCT_ID
|
| +#define XI_PROP_PRODUCT_ID "Device Product ID"
|
| +#endif
|
| +
|
| +namespace ui {
|
| +
|
| +namespace {
|
| +
|
| +// Names of all known internal devices that should not be considered as
|
| +// keyboards.
|
| +// TODO(rsadam@): Identify these devices using udev rules. (Crbug.com/420728.)
|
| +const char* kKnownInvalidKeyboardDeviceNames[] = {"Power Button",
|
| + "Sleep Button",
|
| + "Video Bus",
|
| + "gpio-keys.5",
|
| + "gpio-keys.12",
|
| + "ROCKCHIP-I2S Headset Jack"};
|
| +
|
| +const char* kCachedAtomList[] = {
|
| + "Abs MT Position X",
|
| + "Abs MT Position Y",
|
| + XI_KEYBOARD,
|
| + XI_MOUSE,
|
| + XI_TOUCHPAD,
|
| + XI_TOUCHSCREEN,
|
| + XI_PROP_PRODUCT_ID,
|
| + NULL,
|
| +};
|
| +
|
| +enum DeviceType {
|
| + DEVICE_TYPE_KEYBOARD,
|
| + DEVICE_TYPE_MOUSE,
|
| + DEVICE_TYPE_TOUCHPAD,
|
| + DEVICE_TYPE_TOUCHSCREEN,
|
| + DEVICE_TYPE_OTHER
|
| +};
|
| +
|
| +typedef base::Callback<void(const std::vector<KeyboardDevice>&)>
|
| + KeyboardDeviceCallback;
|
| +
|
| +typedef base::Callback<void(const std::vector<TouchscreenDevice>&)>
|
| + TouchscreenDeviceCallback;
|
| +
|
| +typedef base::Callback<void(const std::vector<InputDevice>&)>
|
| + InputDeviceCallback;
|
| +
|
| +// Used for updating the state on the UI thread once device information is
|
| +// parsed on helper threads.
|
| +struct UiCallbacks {
|
| + KeyboardDeviceCallback keyboard_callback;
|
| + TouchscreenDeviceCallback touchscreen_callback;
|
| + InputDeviceCallback mouse_callback;
|
| + InputDeviceCallback touchpad_callback;
|
| + base::Closure hotplug_finished_callback;
|
| +};
|
| +
|
| +// Stores a copy of the XIValuatorClassInfo values so X11 device processing can
|
| +// happen on a worker thread. This is needed since X11 structs are not copyable.
|
| +struct ValuatorClassInfo {
|
| + ValuatorClassInfo(const XIValuatorClassInfo& info)
|
| + : label(info.label),
|
| + max(info.max),
|
| + min(info.min),
|
| + mode(info.mode),
|
| + number(info.number) {}
|
| +
|
| + Atom label;
|
| + double max;
|
| + double min;
|
| + int mode;
|
| + int number;
|
| +};
|
| +
|
| +// Stores a copy of the XITouchClassInfo values so X11 device processing can
|
| +// happen on a worker thread. This is needed since X11 structs are not copyable.
|
| +struct TouchClassInfo {
|
| + TouchClassInfo() : mode(0), num_touches(0) {}
|
| +
|
| + explicit TouchClassInfo(const XITouchClassInfo& info)
|
| + : mode(info.mode), num_touches(info.num_touches) {}
|
| +
|
| + int mode;
|
| + int num_touches;
|
| +};
|
| +
|
| +struct DeviceInfo {
|
| + DeviceInfo(const XIDeviceInfo& device,
|
| + DeviceType type,
|
| + const base::FilePath& path,
|
| + uint16_t vendor,
|
| + uint16_t product)
|
| + : id(device.deviceid),
|
| + name(device.name),
|
| + vendor_id(vendor),
|
| + product_id(product),
|
| + use(device.use),
|
| + type(type),
|
| + path(path) {
|
| + for (int i = 0; i < device.num_classes; ++i) {
|
| + switch (device.classes[i]->type) {
|
| + case XIValuatorClass:
|
| + valuator_class_infos.push_back(ValuatorClassInfo(
|
| + *reinterpret_cast<XIValuatorClassInfo*>(device.classes[i])));
|
| + break;
|
| + case XITouchClass:
|
| + // A device can have at most one XITouchClassInfo. Ref:
|
| + // http://manpages.ubuntu.com/manpages/saucy/man3/XIQueryDevice.3.html
|
| + DCHECK(!touch_class_info.mode);
|
| + touch_class_info = TouchClassInfo(
|
| + *reinterpret_cast<XITouchClassInfo*>(device.classes[i]));
|
| + break;
|
| + default:
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Unique device identifier.
|
| + int id;
|
| +
|
| + // Internal device name.
|
| + std::string name;
|
| +
|
| + // USB-style device identifiers.
|
| + uint16_t vendor_id;
|
| + uint16_t product_id;
|
| +
|
| + // Device type (ie: XIMasterPointer)
|
| + int use;
|
| +
|
| + // Specifies the type of the device.
|
| + DeviceType type;
|
| +
|
| + // Path to the actual device (ie: /dev/input/eventXX)
|
| + base::FilePath path;
|
| +
|
| + std::vector<ValuatorClassInfo> valuator_class_infos;
|
| +
|
| + TouchClassInfo touch_class_info;
|
| +};
|
| +
|
| +// X11 display cache used on worker threads. This is filled on the UI thread and
|
| +// passed in to the worker threads.
|
| +struct DisplayState {
|
| + Atom mt_position_x;
|
| + Atom mt_position_y;
|
| +};
|
| +
|
| +// Returns true if |name| is the name of a known invalid keyboard device. Note,
|
| +// this may return false negatives.
|
| +bool IsKnownInvalidKeyboardDevice(const std::string& name) {
|
| + std::string trimmed(name);
|
| + base::TrimWhitespaceASCII(name, base::TRIM_TRAILING, &trimmed);
|
| + for (const char* device_name : kKnownInvalidKeyboardDeviceNames) {
|
| + if (trimmed == device_name)
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Returns true if |name| is the name of a known XTEST device. Note, this may
|
| +// return false negatives.
|
| +bool IsTestDevice(const std::string& name) {
|
| + return name.find("XTEST") != std::string::npos;
|
| +}
|
| +
|
| +base::FilePath GetDevicePath(XDisplay* dpy, const XIDeviceInfo& device) {
|
| + // Skip the main pointer and keyboard since XOpenDevice() generates a
|
| + // BadDevice error when passed these devices.
|
| + if (device.use == XIMasterPointer || device.use == XIMasterKeyboard)
|
| + return base::FilePath();
|
| +
|
| + // Input device has a property "Device Node" pointing to its dev input node,
|
| + // e.g. Device Node (250): "/dev/input/event8"
|
| + Atom device_node = XInternAtom(dpy, "Device Node", False);
|
| + if (device_node == None)
|
| + return base::FilePath();
|
| +
|
| + Atom actual_type;
|
| + int actual_format;
|
| + unsigned long nitems, bytes_after;
|
| + unsigned char* data;
|
| + XDevice* dev = XOpenDevice(dpy, device.deviceid);
|
| + if (!dev)
|
| + return base::FilePath();
|
| +
|
| + if (XGetDeviceProperty(dpy,
|
| + dev,
|
| + device_node,
|
| + 0,
|
| + 1000,
|
| + False,
|
| + AnyPropertyType,
|
| + &actual_type,
|
| + &actual_format,
|
| + &nitems,
|
| + &bytes_after,
|
| + &data) != Success) {
|
| + XCloseDevice(dpy, dev);
|
| + return base::FilePath();
|
| + }
|
| +
|
| + std::string path;
|
| + // Make sure the returned value is a string.
|
| + if (actual_type == XA_STRING && actual_format == 8)
|
| + path = reinterpret_cast<char*>(data);
|
| +
|
| + XFree(data);
|
| + XCloseDevice(dpy, dev);
|
| +
|
| + return base::FilePath(path);
|
| +}
|
| +
|
| +// Helper used to parse keyboard information. When it is done it uses
|
| +// |reply_runner| and |callback| to update the state on the UI thread.
|
| +void HandleKeyboardDevicesInWorker(
|
| + const std::vector<DeviceInfo>& device_infos,
|
| + scoped_refptr<base::TaskRunner> reply_runner,
|
| + const KeyboardDeviceCallback& callback) {
|
| + std::vector<KeyboardDevice> devices;
|
| +
|
| + for (const DeviceInfo& device_info : device_infos) {
|
| + if (device_info.type != DEVICE_TYPE_KEYBOARD)
|
| + continue;
|
| + if (device_info.use != XISlaveKeyboard)
|
| + continue; // Assume all keyboards are keyboard slaves
|
| + if (IsKnownInvalidKeyboardDevice(device_info.name))
|
| + continue; // Skip invalid devices.
|
| + InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
|
| + KeyboardDevice keyboard(device_info.id, type, device_info.name);
|
| + devices.push_back(keyboard);
|
| + }
|
| +
|
| + reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
|
| +}
|
| +
|
| +// Helper used to parse mouse information. When it is done it uses
|
| +// |reply_runner| and |callback| to update the state on the UI thread.
|
| +void HandleMouseDevicesInWorker(const std::vector<DeviceInfo>& device_infos,
|
| + scoped_refptr<base::TaskRunner> reply_runner,
|
| + const InputDeviceCallback& callback) {
|
| + std::vector<InputDevice> devices;
|
| + for (const DeviceInfo& device_info : device_infos) {
|
| + if (device_info.type != DEVICE_TYPE_MOUSE ||
|
| + device_info.use != XISlavePointer) {
|
| + continue;
|
| + }
|
| +
|
| + InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
|
| + devices.push_back(InputDevice(device_info.id, type, device_info.name));
|
| + }
|
| +
|
| + reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
|
| +}
|
| +
|
| +// Helper used to parse touchpad information. When it is done it uses
|
| +// |reply_runner| and |callback| to update the state on the UI thread.
|
| +void HandleTouchpadDevicesInWorker(const std::vector<DeviceInfo>& device_infos,
|
| + scoped_refptr<base::TaskRunner> reply_runner,
|
| + const InputDeviceCallback& callback) {
|
| + std::vector<InputDevice> devices;
|
| + for (const DeviceInfo& device_info : device_infos) {
|
| + if (device_info.type != DEVICE_TYPE_TOUCHPAD ||
|
| + device_info.use != XISlavePointer) {
|
| + continue;
|
| + }
|
| +
|
| + InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
|
| + devices.push_back(InputDevice(device_info.id, type, device_info.name));
|
| + }
|
| +
|
| + reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
|
| +}
|
| +
|
| +// Helper used to parse touchscreen information. When it is done it uses
|
| +// |reply_runner| and |callback| to update the state on the UI thread.
|
| +void HandleTouchscreenDevicesInWorker(
|
| + const std::vector<DeviceInfo>& device_infos,
|
| + const DisplayState& display_state,
|
| + scoped_refptr<base::TaskRunner> reply_runner,
|
| + const TouchscreenDeviceCallback& callback) {
|
| + std::vector<TouchscreenDevice> devices;
|
| + if (display_state.mt_position_x == None ||
|
| + display_state.mt_position_y == None)
|
| + return;
|
| +
|
| + for (const DeviceInfo& device_info : device_infos) {
|
| + if (device_info.type != DEVICE_TYPE_TOUCHSCREEN ||
|
| + (device_info.use != XIFloatingSlave &&
|
| + device_info.use != XISlavePointer)) {
|
| + continue;
|
| + }
|
| +
|
| + // Touchscreens should be direct touch devices.
|
| + if (device_info.touch_class_info.mode != XIDirectTouch)
|
| + continue;
|
| +
|
| + double max_x = -1.0;
|
| + double max_y = -1.0;
|
| +
|
| + for (const ValuatorClassInfo& valuator : device_info.valuator_class_infos) {
|
| + if (display_state.mt_position_x == valuator.label) {
|
| + // Ignore X axis valuator with unexpected properties
|
| + if (valuator.number == 0 && valuator.mode == Absolute &&
|
| + valuator.min == 0.0) {
|
| + max_x = valuator.max;
|
| + }
|
| + } else if (display_state.mt_position_y == valuator.label) {
|
| + // Ignore Y axis valuator with unexpected properties
|
| + if (valuator.number == 1 && valuator.mode == Absolute &&
|
| + valuator.min == 0.0) {
|
| + max_y = valuator.max;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Touchscreens should have absolute X and Y axes.
|
| + if (max_x > 0.0 && max_y > 0.0) {
|
| + InputDeviceType type = GetInputDeviceTypeFromPath(device_info.path);
|
| + // |max_x| and |max_y| are inclusive values, so we need to add 1 to get
|
| + // the size.
|
| + devices.push_back(
|
| + TouchscreenDevice(device_info.id, type, device_info.name,
|
| + gfx::Size(max_x + 1, max_y + 1),
|
| + device_info.touch_class_info.num_touches));
|
| + }
|
| + }
|
| +
|
| + reply_runner->PostTask(FROM_HERE, base::Bind(callback, devices));
|
| +}
|
| +
|
| +// Called on a worker thread to parse the device information.
|
| +void HandleHotplugEventInWorker(
|
| + const std::vector<DeviceInfo>& devices,
|
| + const DisplayState& display_state,
|
| + scoped_refptr<base::TaskRunner> reply_runner,
|
| + const UiCallbacks& callbacks) {
|
| + HandleTouchscreenDevicesInWorker(
|
| + devices, display_state, reply_runner, callbacks.touchscreen_callback);
|
| + HandleKeyboardDevicesInWorker(
|
| + devices, reply_runner, callbacks.keyboard_callback);
|
| + HandleMouseDevicesInWorker(devices, reply_runner, callbacks.mouse_callback);
|
| + HandleTouchpadDevicesInWorker(devices, reply_runner,
|
| + callbacks.touchpad_callback);
|
| + reply_runner->PostTask(FROM_HERE, callbacks.hotplug_finished_callback);
|
| +}
|
| +
|
| +DeviceHotplugEventObserver* GetHotplugEventObserver() {
|
| + return DeviceDataManager::GetInstance();
|
| +}
|
| +
|
| +void OnKeyboardDevices(const std::vector<KeyboardDevice>& devices) {
|
| + GetHotplugEventObserver()->OnKeyboardDevicesUpdated(devices);
|
| +}
|
| +
|
| +void OnTouchscreenDevices(const std::vector<TouchscreenDevice>& devices) {
|
| + GetHotplugEventObserver()->OnTouchscreenDevicesUpdated(devices);
|
| +}
|
| +
|
| +void OnMouseDevices(const std::vector<InputDevice>& devices) {
|
| + GetHotplugEventObserver()->OnMouseDevicesUpdated(devices);
|
| +}
|
| +
|
| +void OnTouchpadDevices(const std::vector<InputDevice>& devices) {
|
| + GetHotplugEventObserver()->OnTouchpadDevicesUpdated(devices);
|
| +}
|
| +
|
| +void OnHotplugFinished() {
|
| + GetHotplugEventObserver()->OnDeviceListsComplete();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +X11HotplugEventHandler::X11HotplugEventHandler()
|
| + : atom_cache_(gfx::GetXDisplay(), kCachedAtomList) {
|
| +}
|
| +
|
| +X11HotplugEventHandler::~X11HotplugEventHandler() {
|
| +}
|
| +
|
| +void X11HotplugEventHandler::OnHotplugEvent() {
|
| + Display* display = gfx::GetXDisplay();
|
| + const XDeviceList& device_list_xi =
|
| + DeviceListCacheX11::GetInstance()->GetXDeviceList(display);
|
| + const XIDeviceList& device_list_xi2 =
|
| + DeviceListCacheX11::GetInstance()->GetXI2DeviceList(display);
|
| +
|
| + const int kMaxDeviceNum = 128;
|
| + DeviceType device_types[kMaxDeviceNum];
|
| + for (int i = 0; i < kMaxDeviceNum; ++i)
|
| + device_types[i] = DEVICE_TYPE_OTHER;
|
| +
|
| + for (int i = 0; i < device_list_xi.count; ++i) {
|
| + int id = device_list_xi[i].id;
|
| + if (id < 0 || id >= kMaxDeviceNum)
|
| + continue;
|
| +
|
| + Atom type = device_list_xi[i].type;
|
| + if (type == atom_cache_.GetAtom(XI_KEYBOARD))
|
| + device_types[id] = DEVICE_TYPE_KEYBOARD;
|
| + else if (type == atom_cache_.GetAtom(XI_MOUSE))
|
| + device_types[id] = DEVICE_TYPE_MOUSE;
|
| + else if (type == atom_cache_.GetAtom(XI_TOUCHPAD))
|
| + device_types[id] = DEVICE_TYPE_TOUCHPAD;
|
| + else if (type == atom_cache_.GetAtom(XI_TOUCHSCREEN))
|
| + device_types[id] = DEVICE_TYPE_TOUCHSCREEN;
|
| + }
|
| +
|
| + std::vector<DeviceInfo> device_infos;
|
| + for (int i = 0; i < device_list_xi2.count; ++i) {
|
| + const XIDeviceInfo& device = device_list_xi2[i];
|
| + if (!device.enabled || IsTestDevice(device.name))
|
| + continue;
|
| +
|
| + DeviceType device_type =
|
| + (device.deviceid >= 0 && device.deviceid < kMaxDeviceNum)
|
| + ? device_types[device.deviceid]
|
| + : DEVICE_TYPE_OTHER;
|
| +
|
| + // Obtain the USB-style vendor and product identifiers.
|
| + // (On Linux, XI2 makes this available for all evdev devices.
|
| + uint32_t* product_info;
|
| + Atom type;
|
| + int format_return;
|
| + unsigned long num_items_return;
|
| + unsigned long bytes_after_return;
|
| + uint16_t vendor = 0;
|
| + uint16_t product = 0;
|
| + if (XIGetProperty(gfx::GetXDisplay(), device.deviceid,
|
| + atom_cache_.GetAtom(XI_PROP_PRODUCT_ID), 0, 2, 0,
|
| + XA_INTEGER, &type, &format_return, &num_items_return,
|
| + &bytes_after_return,
|
| + reinterpret_cast<unsigned char**>(&product_info)) == 0 &&
|
| + product_info) {
|
| + if (num_items_return == 2) {
|
| + vendor = product_info[0];
|
| + product = product_info[1];
|
| + }
|
| + XFree(product_info);
|
| + }
|
| +
|
| + device_infos.push_back(DeviceInfo(
|
| + device, device_type, GetDevicePath(display, device), vendor, product));
|
| + }
|
| +
|
| + // X11 is not thread safe, so first get all the required state.
|
| + DisplayState display_state;
|
| + display_state.mt_position_x = atom_cache_.GetAtom("Abs MT Position X");
|
| + display_state.mt_position_y = atom_cache_.GetAtom("Abs MT Position Y");
|
| +
|
| + UiCallbacks callbacks;
|
| + callbacks.keyboard_callback = base::Bind(&OnKeyboardDevices);
|
| + callbacks.touchscreen_callback = base::Bind(&OnTouchscreenDevices);
|
| + callbacks.mouse_callback = base::Bind(&OnMouseDevices);
|
| + callbacks.touchpad_callback = base::Bind(&OnTouchpadDevices);
|
| + callbacks.hotplug_finished_callback = base::Bind(&OnHotplugFinished);
|
| +
|
| + // Parsing the device information may block, so delegate the operation to a
|
| + // worker thread. Once the device information is extracted the parsed devices
|
| + // will be returned via the callbacks.
|
| + base::WorkerPool::PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&HandleHotplugEventInWorker, device_infos, display_state,
|
| + base::ThreadTaskRunnerHandle::Get(), callbacks),
|
| + true /* task_is_slow */);
|
| +}
|
| +
|
| +} // namespace ui
|
|
|