diff options
author | spang@chromium.org <spang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-03 20:06:48 +0000 |
---|---|---|
committer | spang@chromium.org <spang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-03 20:06:48 +0000 |
commit | 1247de8adb3acfbe1b26416a4e4a89aacab87388 (patch) | |
tree | 2cb3cbace0c42e86b3463d91a92a10d7b621bf6b /ui | |
parent | 93b89a40425cb8b0c0e336211ee8b9fd621fc341 (diff) | |
download | chromium_src-1247de8adb3acfbe1b26416a4e4a89aacab87388.zip chromium_src-1247de8adb3acfbe1b26416a4e4a89aacab87388.tar.gz chromium_src-1247de8adb3acfbe1b26416a4e4a89aacab87388.tar.bz2 |
evdev: Support device hotplug with udev
This watches for new input devices and opens any that appear for event
dispatching. This is only supported when use_udev==1.
Review URL: https://codereview.chromium.org/150633005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248560 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r-- | ui/events/events.gyp | 8 | ||||
-rw-r--r-- | ui/events/ozone/evdev/device_manager_udev.cc | 207 | ||||
-rw-r--r-- | ui/events/ozone/evdev/device_manager_udev.h | 18 | ||||
-rw-r--r-- | ui/events/ozone/evdev/event_device_info.cc | 4 | ||||
-rw-r--r-- | ui/events/ozone/evdev/event_factory.cc | 153 | ||||
-rw-r--r-- | ui/events/ozone/evdev/event_factory.h | 34 | ||||
-rw-r--r-- | ui/events/ozone/evdev/key_event_converter.cc | 3 | ||||
-rw-r--r-- | ui/events/ozone/evdev/scoped_udev.h | 8 | ||||
-rw-r--r-- | ui/events/ozone/evdev/touch_event_converter.cc | 3 |
9 files changed, 301 insertions, 137 deletions
diff --git a/ui/events/events.gyp b/ui/events/events.gyp index bcfc0f2..73d4d14a 100644 --- a/ui/events/events.gyp +++ b/ui/events/events.gyp @@ -112,6 +112,8 @@ 'gestures/gesture_types.h', 'gestures/velocity_calculator.cc', 'gestures/velocity_calculator.h', + 'ozone/evdev/device_manager_udev.cc', + 'ozone/evdev/device_manager_udev.h', 'ozone/evdev/event_converter.cc', 'ozone/evdev/event_converter.h', 'ozone/evdev/event_device_info.cc', @@ -151,6 +153,12 @@ '<(DEPTH)/build/linux/system.gyp:udev', ], }], + ['use_udev==0', { + 'sources!': [ + 'ozone/evdev/device_manager_udev.cc', + 'ozone/evdev/device_manager_udev.h', + ], + }], ], }, { diff --git a/ui/events/ozone/evdev/device_manager_udev.cc b/ui/events/ozone/evdev/device_manager_udev.cc new file mode 100644 index 0000000..6f5864e --- /dev/null +++ b/ui/events/ozone/evdev/device_manager_udev.cc @@ -0,0 +1,207 @@ +// 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/ozone/evdev/device_manager_udev.h" + +#include <libudev.h> + +#include "base/files/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump_ozone.h" +#include "base/strings/stringprintf.h" +#include "ui/events/ozone/evdev/event_factory.h" +#include "ui/events/ozone/evdev/scoped_udev.h" + +namespace ui { + +namespace { + +const char kSubsystemInput[] = "input"; + +// Severity levels from syslog.h. We can't include it directly as it +// conflicts with base/logging.h +enum { + SYS_LOG_EMERG = 0, + SYS_LOG_ALERT = 1, + SYS_LOG_CRIT = 2, + SYS_LOG_ERR = 3, + SYS_LOG_WARNING = 4, + SYS_LOG_NOTICE = 5, + SYS_LOG_INFO = 6, + SYS_LOG_DEBUG = 7, +}; + +// Log handler for messages generated from libudev. +void UdevLog(struct udev* udev, + int priority, + const char* file, + int line, + const char* fn, + const char* format, + va_list args) { + std::string message = base::StringPrintf("libudev: %s: ", fn); + base::StringAppendV(&message, format, args); + if (priority <= SYS_LOG_ERR) + LOG(ERROR) << message; + else if (priority <= SYS_LOG_INFO) + VLOG(1) << message; + else // SYS_LOG_DEBUG + VLOG(2) << message; +} + +// Create libudev context. +scoped_udev UdevCreate() { + struct udev* udev = udev_new(); + if (udev) { + udev_set_log_fn(udev, UdevLog); + udev_set_log_priority(udev, SYS_LOG_DEBUG); + } + return scoped_udev(udev); +} + +// Start monitoring input device changes. +scoped_udev_monitor UdevCreateMonitor(struct udev* udev) { + struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (monitor) { + udev_monitor_filter_add_match_subsystem_devtype( + monitor, kSubsystemInput, NULL); + + if (udev_monitor_enable_receiving(monitor)) + LOG(ERROR) << "failed to start receiving events from udev"; + } + + return scoped_udev_monitor(monitor); +} + +// Enumerate all input devices using udev. Calls device_callback per device. +bool UdevEnumerateInputDevices(struct udev* udev, + const EvdevDeviceCallback& device_callback) { + scoped_udev_enumerate enumerate(udev_enumerate_new(udev)); + if (!enumerate) + return false; + + // Build list of devices with subsystem "input". + udev_enumerate_add_match_subsystem(enumerate.get(), kSubsystemInput); + udev_enumerate_scan_devices(enumerate.get()); + + struct udev_list_entry* devices = + udev_enumerate_get_list_entry(enumerate.get()); + struct udev_list_entry* entry; + + // Run callback per device in the list. + udev_list_entry_foreach(entry, devices) { + const char* name = udev_list_entry_get_name(entry); + + scoped_udev_device device(udev_device_new_from_syspath(udev, name)); + if (!device) + continue; + + const char* path = udev_device_get_devnode(device.get()); + if (!path) + continue; + + // Found input device node; attach. + device_callback.Run(base::FilePath(path)); + } + + return true; +} + +// Device enumerator & monitor using udev. +// +// This class enumerates input devices attached to the system using udev. +// +// It also creates & monitors a udev netlink socket and issues callbacks for +// any changes to the set of attached devices. +// +// TODO(spang): Figure out how to test this. +class DeviceManagerUdev : public DeviceManagerEvdev, + base::MessagePumpLibevent::Watcher { + public: + DeviceManagerUdev() {} + virtual ~DeviceManagerUdev() {} + + // Enumerate existing devices & start watching for device changes. + virtual void ScanAndStartMonitoring(const EvdevDeviceCallback& device_added, + const EvdevDeviceCallback& device_removed) + OVERRIDE { + udev_ = UdevCreate(); + if (!udev_) { + LOG(ERROR) << "failed to initialize libudev"; + return; + } + + if (!StartMonitoring(device_added, device_removed)) + LOG(ERROR) << "failed to start monitoring device changes via udev"; + + if (!UdevEnumerateInputDevices(udev_.get(), device_added)) + LOG(ERROR) << "failed to enumerate input devices via udev"; + } + + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE { + // The netlink socket should never become disconnected. There's no need + // to handle broken connections here. + + scoped_udev_device device(udev_monitor_receive_device(udev_monitor_.get())); + if (!device) + return; + + const char* path = udev_device_get_devnode(device.get()); + const char* action = udev_device_get_action(device.get()); + if (!path || !action) + return; + + if (!strcmp(action, "add") || !strcmp(action, "change")) + device_added_.Run(base::FilePath(path)); + else if (!strcmp(action, "remove")) + device_removed_.Run(base::FilePath(path)); + } + + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE { NOTREACHED(); } + + private: + bool StartMonitoring(const EvdevDeviceCallback& device_added, + const EvdevDeviceCallback& device_removed) { + udev_monitor_ = UdevCreateMonitor(udev_.get()); + if (!udev_monitor_) + return false; + + // Grab monitor socket. + int fd = udev_monitor_get_fd(udev_monitor_.get()); + if (fd < 0) + return false; + + // Save callbacks. + device_added_ = device_added; + device_removed_ = device_removed; + + // Watch for incoming events on monitor socket. + // TODO(spang): Hotplug may need to be offloaded from the UI thread. + return base::MessagePumpOzone::Current()->WatchFileDescriptor( + fd, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this); + } + + // Udev daemon connection. + scoped_udev udev_; + + // Udev device change monitor. + scoped_udev_monitor udev_monitor_; + + // Callbacks for device changes. + EvdevDeviceCallback device_added_; + EvdevDeviceCallback device_removed_; + + // Watcher for uevent netlink socket. + base::MessagePumpLibevent::FileDescriptorWatcher controller_; + + DISALLOW_COPY_AND_ASSIGN(DeviceManagerUdev); +}; + +} // namespace + +scoped_ptr<DeviceManagerEvdev> CreateDeviceManagerUdev() { + return scoped_ptr<DeviceManagerEvdev>(new DeviceManagerUdev); +} + +} // namespace ui diff --git a/ui/events/ozone/evdev/device_manager_udev.h b/ui/events/ozone/evdev/device_manager_udev.h new file mode 100644 index 0000000..eb3ee91 --- /dev/null +++ b/ui/events/ozone/evdev/device_manager_udev.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef UI_EVENTS_OZONE_EVDEV_DEVICE_MANAGER_UDEV_H_ +#define UI_EVENTS_OZONE_EVDEV_DEVICE_MANAGER_UDEV_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/ozone/evdev/event_factory.h" + +namespace ui { + +// Constructor for DeviceManagerUdev. +scoped_ptr<DeviceManagerEvdev> CreateDeviceManagerUdev(); + +} // namespace ui + +#endif // UI_EVENTS_OZONE_EVDEV_DEVICE_MANAGER_UDEV_H_ diff --git a/ui/events/ozone/evdev/event_device_info.cc b/ui/events/ozone/evdev/event_device_info.cc index 9621646..3e10d79 100644 --- a/ui/events/ozone/evdev/event_device_info.cc +++ b/ui/events/ozone/evdev/event_device_info.cc @@ -14,8 +14,6 @@ namespace ui { namespace { bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) { - base::ThreadRestrictions::AssertIOAllowed(); - if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) { DLOG(ERROR) << "failed EVIOCGBIT(" << type << ", " << size << ") on fd " << fd; @@ -26,8 +24,6 @@ bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) { } bool GetPropBits(int fd, void* buf, unsigned int size) { - base::ThreadRestrictions::AssertIOAllowed(); - if (ioctl(fd, EVIOCGPROP(size), buf) < 0) { DLOG(ERROR) << "failed EVIOCGPROP(" << size << ") on fd " << fd; return false; diff --git a/ui/events/ozone/evdev/event_factory.cc b/ui/events/ozone/evdev/event_factory.cc index cc92819..a458eab 100644 --- a/ui/events/ozone/evdev/event_factory.cc +++ b/ui/events/ozone/evdev/event_factory.cc @@ -4,26 +4,19 @@ #include "ui/events/ozone/evdev/event_factory.h" -#include <errno.h> #include <fcntl.h> #include <linux/input.h> -#include <poll.h> -#include <unistd.h> -#if defined(USE_UDEV) -#include <libudev.h> -#endif - -#include "base/callback.h" #include "base/debug/trace_event.h" #include "base/files/file_enumerator.h" #include "base/stl_util.h" -#include "base/strings/stringprintf.h" -#include "base/threading/thread_restrictions.h" #include "ui/events/ozone/evdev/event_device_info.h" #include "ui/events/ozone/evdev/key_event_converter.h" #include "ui/events/ozone/evdev/touch_event_converter.h" -#include "ui/events/ozone/event_factory_ozone.h" + +#if defined(USE_UDEV) +#include "ui/events/ozone/evdev/device_manager_udev.h" +#endif namespace ui { @@ -41,86 +34,29 @@ bool IsTouchScreen(const EventDeviceInfo& devinfo) { return devinfo.HasEventType(EV_ABS) && !IsTouchPad(devinfo); } -#if defined(USE_UDEV) - -// Severity levels from syslog.h. We can't include it directly as it -// conflicts with base/logging.h -enum { - SYS_LOG_EMERG = 0, - SYS_LOG_ALERT = 1, - SYS_LOG_CRIT = 2, - SYS_LOG_ERR = 3, - SYS_LOG_WARNING = 4, - SYS_LOG_NOTICE = 5, - SYS_LOG_INFO = 6, - SYS_LOG_DEBUG = 7, -}; - -// Log handler for messages generated from libudev. -void UdevLog(struct udev* udev, - int priority, - const char* file, - int line, - const char* fn, - const char* format, - va_list args) { - std::string message = base::StringPrintf("libudev: %s: ", fn); - base::StringAppendV(&message, format, args); - if (priority <= SYS_LOG_ERR) - LOG(ERROR) << message; - else if (priority <= SYS_LOG_INFO) - VLOG(1) << message; - else // SYS_LOG_DEBUG - VLOG(2) << message; -} - -// Connect to the udev daemon. -scoped_udev UdevConnect() { - struct udev* udev = udev_new(); - if (udev) { - udev_set_log_fn(udev, UdevLog); - udev_set_log_priority(udev, SYS_LOG_DEBUG); +class DeviceManagerManual : public DeviceManagerEvdev { + public: + DeviceManagerManual() {} + virtual ~DeviceManagerManual() {} + + // Enumerate existing devices & start watching for device changes. + virtual void ScanAndStartMonitoring(const EvdevDeviceCallback& device_added, + const EvdevDeviceCallback& device_removed) + OVERRIDE { + base::FileEnumerator file_enum(base::FilePath("/dev/input"), + false, + base::FileEnumerator::FILES, + "event*[0-9]"); + for (base::FilePath path = file_enum.Next(); !path.empty(); + path = file_enum.Next()) + device_added.Run(path); } - return scoped_udev(udev); -} - -// Enumerate all input devices using udev. Calls device_callback per device. -bool UdevEnumerateInputDevices( - struct udev* udev, - base::Callback<void(const base::FilePath&)> device_callback) { - scoped_udev_enumerate enumerate(udev_enumerate_new(udev)); - if (!enumerate) - return false; - - udev_enumerate_add_match_subsystem(enumerate.get(), "input"); - udev_enumerate_scan_devices(enumerate.get()); - - struct udev_list_entry* devices = - udev_enumerate_get_list_entry(enumerate.get()); - struct udev_list_entry* entry; - - udev_list_entry_foreach(entry, devices) { - const char* name = udev_list_entry_get_name(entry); - - scoped_udev_device device(udev_device_new_from_syspath(udev, name)); - if (!device) - continue; - - const char* path = udev_device_get_devnode(device.get()); - if (!path) - continue; - - // Found input device node; attach. - device_callback.Run(base::FilePath(path)); - } - - return true; -} - -#endif // defined(USE_UDEV) +}; } // namespace +DeviceManagerEvdev::~DeviceManagerEvdev() {} + EventFactoryEvdev::EventFactoryEvdev() {} EventFactoryEvdev::~EventFactoryEvdev() { STLDeleteValues(&converters_); } @@ -147,7 +83,7 @@ void EventFactoryEvdev::AttachInputDevice(const base::FilePath& path) { return; } - // TODO(spang) Add more device types. Support hot-plugging. + // TODO(spang) Add more device types. scoped_ptr<EventConverterEvdev> converter; if (IsTouchScreen(devinfo)) converter.reset(new TouchEventConverterEvdev(fd, path)); @@ -155,47 +91,32 @@ void EventFactoryEvdev::AttachInputDevice(const base::FilePath& path) { converter.reset(new KeyEventConverterEvdev(fd, path, &modifiers_)); if (converter) { + delete converters_[path]; converters_[path] = converter.release(); } else { close(fd); } } -void EventFactoryEvdev::StartProcessingEvents() { - base::ThreadRestrictions::AssertIOAllowed(); +void EventFactoryEvdev::DetachInputDevice(const base::FilePath& path) { + TRACE_EVENT1("ozone", "DetachInputDevice", "path", path.value()); + delete converters_[path]; + converters_.erase(path); +} +void EventFactoryEvdev::StartProcessingEvents() { #if defined(USE_UDEV) // Scan for input devices using udev. - StartProcessingEventsUdev(); + device_manager_ = CreateDeviceManagerUdev(); #else // No udev support. Scan devices manually in /dev/input. - StartProcessingEventsManual(); + device_manager_.reset(new DeviceManagerManual); #endif -} -void EventFactoryEvdev::StartProcessingEventsManual() { - base::FileEnumerator file_enum(base::FilePath("/dev/input"), - false, - base::FileEnumerator::FILES, - "event*[0-9]"); - for (base::FilePath path = file_enum.Next(); !path.empty(); - path = file_enum.Next()) - AttachInputDevice(path); + device_manager_->ScanAndStartMonitoring( + base::Bind(&EventFactoryEvdev::AttachInputDevice, base::Unretained(this)), + base::Bind(&EventFactoryEvdev::DetachInputDevice, + base::Unretained(this))); } -#if defined(USE_UDEV) -void EventFactoryEvdev::StartProcessingEventsUdev() { - udev_ = UdevConnect(); - if (!udev_) { - LOG(ERROR) << "failed to connect to udev"; - return; - } - if (!UdevEnumerateInputDevices( - udev_.get(), - base::Bind(&EventFactoryEvdev::AttachInputDevice, - base::Unretained(this)))) - LOG(ERROR) << "failed to enumerate input devices via udev"; -} -#endif - } // namespace ui diff --git a/ui/events/ozone/evdev/event_factory.h b/ui/events/ozone/evdev/event_factory.h index a0fbbe4..2bcda41 100644 --- a/ui/events/ozone/evdev/event_factory.h +++ b/ui/events/ozone/evdev/event_factory.h @@ -5,6 +5,7 @@ #ifndef UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_ #define UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_ +#include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "ui/events/events_export.h" @@ -12,12 +13,22 @@ #include "ui/events/ozone/evdev/event_modifiers.h" #include "ui/events/ozone/event_factory_ozone.h" -#if defined(USE_UDEV) -#include "ui/events/ozone/evdev/scoped_udev.h" -#endif - namespace ui { +typedef base::Callback<void(const base::FilePath& file_path)> + EvdevDeviceCallback; + +// Interface for scanning & monitoring input devices. +class DeviceManagerEvdev { + public: + virtual ~DeviceManagerEvdev(); + + // Enumerate devices & start watching for changes. + virtual void ScanAndStartMonitoring( + const EvdevDeviceCallback& device_added, + const EvdevDeviceCallback& device_removed) = 0; +}; + // Ozone events implementation for the Linux input subsystem ("evdev"). class EVENTS_EXPORT EventFactoryEvdev : public EventFactoryOzone { public: @@ -30,21 +41,14 @@ class EVENTS_EXPORT EventFactoryEvdev : public EventFactoryOzone { // Open device at path & starting processing events. void AttachInputDevice(const base::FilePath& file_path); - // Scan & open devices in /dev/input (without udev). - void StartProcessingEventsManual(); - -#if defined(USE_UDEV) - // Scan & open devices using udev. - void StartProcessingEventsUdev(); -#endif + // Close device at path. + void DetachInputDevice(const base::FilePath& file_path); // Owned per-device event converters (by path). std::map<base::FilePath, EventConverterEvdev*> converters_; -#if defined(USE_UDEV) - // Udev daemon connection. - scoped_udev udev_; -#endif + // Interface for scanning & monitoring input devices. + scoped_ptr<DeviceManagerEvdev> device_manager_; EventModifiersEvdev modifiers_; diff --git a/ui/events/ozone/evdev/key_event_converter.cc b/ui/events/ozone/evdev/key_event_converter.cc index b99200e..7949727 100644 --- a/ui/events/ozone/evdev/key_event_converter.cc +++ b/ui/events/ozone/evdev/key_event_converter.cc @@ -215,7 +215,8 @@ void KeyEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { if (read_size < 0) { if (errno == EINTR || errno == EAGAIN) return; - PLOG(ERROR) << "error reading device " << path_.value(); + if (errno != ENODEV) + PLOG(ERROR) << "error reading device " << path_.value(); Stop(); return; } diff --git a/ui/events/ozone/evdev/scoped_udev.h b/ui/events/ozone/evdev/scoped_udev.h index c719508..21063e3 100644 --- a/ui/events/ozone/evdev/scoped_udev.h +++ b/ui/events/ozone/evdev/scoped_udev.h @@ -37,6 +37,14 @@ struct UdevEnumerateDeleter { typedef scoped_ptr<struct udev_enumerate, UdevEnumerateDeleter> scoped_udev_enumerate; +// Scoped struct udev_monitor. +struct UdevMonitorDeleter { + void operator()(struct udev_monitor* udev_monitor) { + udev_monitor_unref(udev_monitor); + } +}; +typedef scoped_ptr<struct udev_monitor, UdevMonitorDeleter> scoped_udev_monitor; + } // namespace ui #endif // UI_EVENTS_OZONE_EVDEV_SCOPED_UDEV_H_ diff --git a/ui/events/ozone/evdev/touch_event_converter.cc b/ui/events/ozone/evdev/touch_event_converter.cc index 931187f..f29525f 100644 --- a/ui/events/ozone/evdev/touch_event_converter.cc +++ b/ui/events/ozone/evdev/touch_event_converter.cc @@ -121,7 +121,8 @@ void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { if (read_size < 0) { if (errno == EINTR || errno == EAGAIN) return; - PLOG(ERROR) << "error reading device " << path_.value(); + if (errno != ENODEV) + PLOG(ERROR) << "error reading device " << path_.value(); Stop(); return; } |