Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(503)

Unified Diff: ui/events/platform/x11/x11_hotplug_event_handler.cc

Issue 1287103004: Sync ui/events to chromium @ https://codereview.chromium.org/1210203002 (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: rebased Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « ui/events/platform/x11/x11_hotplug_event_handler.h ('k') | ui/events/test/event_generator.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « ui/events/platform/x11/x11_hotplug_event_handler.h ('k') | ui/events/test/event_generator.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698