blob: 0a0d674ef38767fad312b57e19d9516590425fa1 [file] [log] [blame]
// 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/event_factory_evdev.h"
#include <fcntl.h>
#include <linux/input.h>
#include "base/debug/trace_event.h"
#include "base/stl_util.h"
#include "base/task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/worker_pool.h"
#include "ui/events/device_data_manager.h"
#include "ui/events/ozone/device/device_event.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
#include "ui/events/ozone/evdev/event_device_info.h"
#include "ui/events/ozone/evdev/key_event_converter_evdev.h"
#include "ui/events/ozone/evdev/touch_event_converter_evdev.h"
#if defined(USE_EVDEV_GESTURES)
#include "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h"
#include "ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h"
#include "ui/events/ozone/evdev/libgestures_glue/gesture_property_provider.h"
#endif
#ifndef EVIOCSCLOCKID
#define EVIOCSCLOCKID _IOW('E', 0xa0, int)
#endif
namespace ui {
namespace {
typedef base::Callback<void(scoped_ptr<EventConverterEvdev>)>
OpenInputDeviceReplyCallback;
struct OpenInputDeviceParams {
// Unique identifier for the new device.
int id;
// Device path to open.
base::FilePath path;
// Callback for dispatching events. Call on UI thread only.
EventDispatchCallback dispatch_callback;
// State shared between devices. Must not be dereferenced on worker thread.
EventModifiersEvdev* modifiers;
KeyboardEvdev* keyboard;
CursorDelegateEvdev* cursor;
#if defined(USE_EVDEV_GESTURES)
GesturePropertyProvider* gesture_property_provider;
#endif
};
#if defined(USE_EVDEV_GESTURES)
bool UseGesturesLibraryForDevice(const EventDeviceInfo& devinfo) {
if (devinfo.HasAbsXY() && !devinfo.IsMappedToScreen())
return true; // touchpad
if (devinfo.HasRelXY())
return true; // mouse
return false;
}
#endif
scoped_ptr<EventConverterEvdev> CreateConverter(
const OpenInputDeviceParams& params,
int fd,
const EventDeviceInfo& devinfo) {
#if defined(USE_EVDEV_GESTURES)
// Touchpad or mouse: use gestures library.
// EventReaderLibevdevCros -> GestureInterpreterLibevdevCros -> DispatchEvent
if (UseGesturesLibraryForDevice(devinfo)) {
scoped_ptr<GestureInterpreterLibevdevCros> gesture_interp = make_scoped_ptr(
new GestureInterpreterLibevdevCros(params.id,
params.modifiers,
params.cursor,
params.keyboard,
params.gesture_property_provider,
params.dispatch_callback));
return make_scoped_ptr(new EventReaderLibevdevCros(
fd, params.path, params.id, gesture_interp.Pass()));
}
#endif
// Touchscreen: use TouchEventConverterEvdev.
scoped_ptr<EventConverterEvdev> converter;
if (devinfo.HasAbsXY())
return make_scoped_ptr<EventConverterEvdev>(new TouchEventConverterEvdev(
fd, params.path, params.id, devinfo, params.dispatch_callback));
// Everything else: use KeyEventConverterEvdev.
return make_scoped_ptr<EventConverterEvdev>(
new KeyEventConverterEvdev(fd, params.path, params.id, params.keyboard));
}
// Open an input device. Opening may put the calling thread to sleep, and
// therefore should be run on a thread where latency is not critical. We
// run it on a thread from the worker pool.
//
// This takes a TaskRunner and runs the reply on that thread, so that we
// can hop threads if necessary (back to the UI thread).
void OpenInputDevice(scoped_ptr<OpenInputDeviceParams> params,
scoped_refptr<base::TaskRunner> reply_runner,
const OpenInputDeviceReplyCallback& reply_callback) {
const base::FilePath& path = params->path;
TRACE_EVENT1("ozone", "OpenInputDevice", "path", path.value());
int fd = open(path.value().c_str(), O_RDONLY | O_NONBLOCK);
if (fd < 0) {
PLOG(ERROR) << "Cannot open '" << path.value();
return;
}
// Use monotonic timestamps for events. The touch code in particular
// expects event timestamps to correlate to the monotonic clock
// (base::TimeTicks).
unsigned int clk = CLOCK_MONOTONIC;
if (ioctl(fd, EVIOCSCLOCKID, &clk))
PLOG(ERROR) << "failed to set CLOCK_MONOTONIC";
EventDeviceInfo devinfo;
if (!devinfo.Initialize(fd)) {
LOG(ERROR) << "failed to get device information for " << path.value();
close(fd);
return;
}
scoped_ptr<EventConverterEvdev> converter =
CreateConverter(*params, fd, devinfo);
// Reply with the constructed converter.
reply_runner->PostTask(FROM_HERE,
base::Bind(reply_callback, base::Passed(&converter)));
}
// Close an input device. Closing may put the calling thread to sleep, and
// therefore should be run on a thread where latency is not critical. We
// run it on the FILE thread.
void CloseInputDevice(const base::FilePath& path,
scoped_ptr<EventConverterEvdev> converter) {
TRACE_EVENT1("ozone", "CloseInputDevice", "path", path.value());
converter.reset();
}
} // namespace
EventFactoryEvdev::EventFactoryEvdev(CursorDelegateEvdev* cursor,
DeviceManager* device_manager)
: last_device_id_(0),
device_manager_(device_manager),
dispatch_callback_(
base::Bind(&EventFactoryEvdev::PostUiEvent, base::Unretained(this))),
keyboard_(&modifiers_, dispatch_callback_),
cursor_(cursor),
#if defined(USE_EVDEV_GESTURES)
gesture_property_provider_(new GesturePropertyProvider),
#endif
weak_ptr_factory_(this) {
DCHECK(device_manager_);
}
EventFactoryEvdev::~EventFactoryEvdev() { STLDeleteValues(&converters_); }
void EventFactoryEvdev::PostUiEvent(scoped_ptr<Event> event) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&EventFactoryEvdev::DispatchUiEventTask,
weak_ptr_factory_.GetWeakPtr(),
base::Passed(&event)));
}
void EventFactoryEvdev::DispatchUiEventTask(scoped_ptr<Event> event) {
DispatchEvent(event.get());
}
void EventFactoryEvdev::AttachInputDevice(
scoped_ptr<EventConverterEvdev> converter) {
const base::FilePath& path = converter->path();
TRACE_EVENT1("ozone", "AttachInputDevice", "path", path.value());
DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
// If we have an existing device, detach it. We don't want two
// devices with the same name open at the same time.
if (converters_[path])
DetachInputDevice(path);
// Add initialized device to map.
converters_[path] = converter.release();
converters_[path]->Start();
NotifyHotplugEventObserver(*converters_[path]);
}
void EventFactoryEvdev::OnDeviceEvent(const DeviceEvent& event) {
if (event.device_type() != DeviceEvent::INPUT)
return;
switch (event.action_type()) {
case DeviceEvent::ADD:
case DeviceEvent::CHANGE: {
TRACE_EVENT1("ozone", "OnDeviceAdded", "path", event.path().value());
scoped_ptr<OpenInputDeviceParams> params(new OpenInputDeviceParams);
params->id = NextDeviceId();
params->path = event.path();
params->dispatch_callback = dispatch_callback_;
params->modifiers = &modifiers_;
params->keyboard = &keyboard_;
params->cursor = cursor_;
#if defined(USE_EVDEV_GESTURES)
params->gesture_property_provider = gesture_property_provider_.get();
#endif
OpenInputDeviceReplyCallback reply_callback =
base::Bind(&EventFactoryEvdev::AttachInputDevice,
weak_ptr_factory_.GetWeakPtr());
// Dispatch task to open from the worker pool, since open may block.
base::WorkerPool::PostTask(FROM_HERE,
base::Bind(&OpenInputDevice,
base::Passed(&params),
ui_task_runner_,
reply_callback),
true /* task_is_slow */);
}
break;
case DeviceEvent::REMOVE: {
TRACE_EVENT1("ozone", "OnDeviceRemoved", "path", event.path().value());
DetachInputDevice(event.path());
}
break;
}
}
void EventFactoryEvdev::OnDispatcherListChanged() {
if (!ui_task_runner_.get()) {
ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
// Scan & monitor devices.
device_manager_->AddObserver(this);
device_manager_->ScanDevices(this);
}
}
void EventFactoryEvdev::DetachInputDevice(const base::FilePath& path) {
TRACE_EVENT1("ozone", "DetachInputDevice", "path", path.value());
DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
// Remove device from map.
scoped_ptr<EventConverterEvdev> converter(converters_[path]);
converters_.erase(path);
if (converter) {
// Cancel libevent notifications from this converter. This part must be
// on UI since the polling happens on UI.
converter->Stop();
NotifyHotplugEventObserver(*converter);
// Dispatch task to close from the worker pool, since close may block.
base::WorkerPool::PostTask(
FROM_HERE,
base::Bind(&CloseInputDevice, path, base::Passed(&converter)),
true);
}
}
void EventFactoryEvdev::WarpCursorTo(gfx::AcceleratedWidget widget,
const gfx::PointF& location) {
if (cursor_) {
cursor_->MoveCursorTo(widget, location);
PostUiEvent(make_scoped_ptr(new MouseEvent(ET_MOUSE_MOVED,
cursor_->location(),
cursor_->location(),
modifiers_.GetModifierFlags(),
/* changed_button_flags */ 0)));
}
}
void EventFactoryEvdev::NotifyHotplugEventObserver(
const EventConverterEvdev& converter) {
// For now the only information propagated is related to touchscreens. Ignore
// events for everything but touchscreens.
if (!converter.HasTouchscreen())
return;
DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
std::vector<TouchscreenDevice> touchscreens;
for (auto it = converters_.begin(); it != converters_.end(); ++it) {
if (it->second->HasTouchscreen()) {
touchscreens.push_back(
TouchscreenDevice(it->second->id(),
InputDeviceType::INPUT_DEVICE_EXTERNAL,
std::string(), /* Device name */
it->second->GetTouchscreenSize()));
}
}
observer->OnTouchscreenDevicesUpdated(touchscreens);
}
int EventFactoryEvdev::NextDeviceId() {
return ++last_device_id_;
}
} // namespace ui