// 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 #include #include #include #include #include #include #include #include #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&)> KeyboardDeviceCallback; typedef base::Callback&)> TouchscreenDeviceCallback; typedef base::Callback&)> 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(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(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 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(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& device_infos, scoped_refptr reply_runner, const KeyboardDeviceCallback& callback) { std::vector 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& device_infos, scoped_refptr reply_runner, const InputDeviceCallback& callback) { std::vector 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& device_infos, scoped_refptr reply_runner, const InputDeviceCallback& callback) { std::vector 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& device_infos, const DisplayState& display_state, scoped_refptr reply_runner, const TouchscreenDeviceCallback& callback) { std::vector 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& devices, const DisplayState& display_state, scoped_refptr 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& devices) { GetHotplugEventObserver()->OnKeyboardDevicesUpdated(devices); } void OnTouchscreenDevices(const std::vector& devices) { GetHotplugEventObserver()->OnTouchscreenDevicesUpdated(devices); } void OnMouseDevices(const std::vector& devices) { GetHotplugEventObserver()->OnMouseDevicesUpdated(devices); } void OnTouchpadDevices(const std::vector& 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 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(&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