blob: 797a5a1b40c6b27007f4ca2b078601d2123355c9 [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/touch_event_converter_evdev.h"
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <poll.h>
#include <stdio.h>
#include <unistd.h>
#include <cmath>
#include <limits>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_switches.h"
#include "ui/gfx/screen.h"
namespace {
struct TouchCalibration {
int bezel_left;
int bezel_right;
int bezel_top;
int bezel_bottom;
};
void GetTouchCalibration(TouchCalibration* cal) {
std::vector<std::string> parts;
if (Tokenize(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kTouchCalibration),
",",
&parts) >= 4) {
if (!base::StringToInt(parts[0], &cal->bezel_left))
DLOG(ERROR) << "Incorrect left border calibration value passed.";
if (!base::StringToInt(parts[1], &cal->bezel_right))
DLOG(ERROR) << "Incorrect right border calibration value passed.";
if (!base::StringToInt(parts[2], &cal->bezel_top))
DLOG(ERROR) << "Incorrect top border calibration value passed.";
if (!base::StringToInt(parts[3], &cal->bezel_bottom))
DLOG(ERROR) << "Incorrect bottom border calibration value passed.";
}
}
float TuxelsToPixels(float val,
float min_tuxels,
float num_tuxels,
float min_pixels,
float num_pixels) {
// Map [min_tuxels, min_tuxels + num_tuxels) to
// [min_pixels, min_pixels + num_pixels).
return min_pixels + (val - min_tuxels) * num_pixels / num_tuxels;
}
float TuxelToPixelSize(float val, float num_tuxels, float num_pixels) {
return val * num_pixels / num_tuxels;
}
} // namespace
namespace ui {
TouchEventConverterEvdev::InProgressEvents::InProgressEvents()
: x_(0),
y_(0),
id_(-1),
finger_(-1),
type_(ET_UNKNOWN),
radius_x_(0),
radius_y_(0),
pressure_(0) {
}
TouchEventConverterEvdev::TouchEventConverterEvdev(
int fd,
base::FilePath path,
int id,
const EventDeviceInfo& info,
const EventDispatchCallback& callback)
: EventConverterEvdev(fd, path, id),
callback_(callback),
syn_dropped_(false),
is_type_a_(false),
current_slot_(0) {
Init(info);
}
TouchEventConverterEvdev::~TouchEventConverterEvdev() {
Stop();
close(fd_);
}
void TouchEventConverterEvdev::Init(const EventDeviceInfo& info) {
gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
if (!screen)
return; // No scaling.
gfx::Display display = screen->GetPrimaryDisplay();
gfx::Size size = display.GetSizeInPixel();
pressure_min_ = info.GetAbsMinimum(ABS_MT_PRESSURE);
pressure_max_ = info.GetAbsMaximum(ABS_MT_PRESSURE);
x_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_X);
x_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_X) - x_min_tuxels_ + 1;
y_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_Y);
y_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_Y) - y_min_tuxels_ + 1;
native_size_ = gfx::Size(x_num_tuxels_, y_num_tuxels_);
// Map coordinates onto screen.
x_min_pixels_ = 0;
y_min_pixels_ = 0;
x_num_pixels_ = size.width();
y_num_pixels_ = size.height();
VLOG(1) << "mapping touch coordinates to screen coordinates: "
<< base::StringPrintf("%dx%d", size.width(), size.height());
// Apply --touch-calibration.
TouchCalibration cal = {};
GetTouchCalibration(&cal);
x_min_tuxels_ += cal.bezel_left;
x_num_tuxels_ -= cal.bezel_left + cal.bezel_right;
y_min_tuxels_ += cal.bezel_top;
y_num_tuxels_ -= cal.bezel_top + cal.bezel_bottom;
VLOG(1) << "applying touch calibration: "
<< base::StringPrintf("[%d, %d, %d, %d]",
cal.bezel_left,
cal.bezel_right,
cal.bezel_top,
cal.bezel_bottom);
for (int i = 0;
i < std::min<int>(info.GetAbsMaximum(ABS_MT_SLOT) + 1, MAX_FINGERS);
++i) {
events_[i].finger_ = info.GetSlotValue(ABS_MT_TRACKING_ID, i);
events_[i].type_ =
events_[i].finger_ < 0 ? ET_TOUCH_RELEASED : ET_TOUCH_PRESSED;
events_[i].x_ = info.GetSlotValue(ABS_MT_POSITION_X, i);
events_[i].y_ = info.GetSlotValue(ABS_MT_POSITION_Y, i);
events_[i].radius_x_ = info.GetSlotValue(ABS_MT_TOUCH_MAJOR, i);
events_[i].radius_y_ = info.GetSlotValue(ABS_MT_TOUCH_MINOR, i);
events_[i].pressure_ = info.GetSlotValue(ABS_MT_PRESSURE, i);
}
}
bool TouchEventConverterEvdev::Reinitialize() {
EventDeviceInfo info;
if (info.Initialize(fd_)) {
Init(info);
return true;
}
return false;
}
bool TouchEventConverterEvdev::HasTouchscreen() const {
return true;
}
gfx::Size TouchEventConverterEvdev::GetTouchscreenSize() const {
return native_size_;
}
void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) {
input_event inputs[MAX_FINGERS * 6 + 1];
ssize_t read_size = read(fd, inputs, sizeof(inputs));
if (read_size < 0) {
if (errno == EINTR || errno == EAGAIN)
return;
if (errno != ENODEV)
PLOG(ERROR) << "error reading device " << path_.value();
Stop();
return;
}
for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) {
ProcessInputEvent(inputs[i]);
}
}
void TouchEventConverterEvdev::ProcessInputEvent(const input_event& input) {
if (input.type == EV_SYN) {
ProcessSyn(input);
} else if(syn_dropped_) {
// Do nothing. This branch indicates we have lost sync with the driver.
} else if (input.type == EV_ABS) {
if (current_slot_ >= MAX_FINGERS) {
LOG(ERROR) << "too many touch events: " << current_slot_;
return;
}
ProcessAbs(input);
} else if (input.type == EV_KEY) {
switch (input.code) {
case BTN_TOUCH:
break;
default:
NOTIMPLEMENTED() << "invalid code for EV_KEY: " << input.code;
}
} else {
NOTIMPLEMENTED() << "invalid type: " << input.type;
}
}
void TouchEventConverterEvdev::ProcessAbs(const input_event& input) {
switch (input.code) {
case ABS_MT_TOUCH_MAJOR:
altered_slots_.set(current_slot_);
// TODO(spang): If we have all of major, minor, and orientation,
// we can scale the ellipse correctly. However on the Pixel we get
// neither minor nor orientation, so this is all we can do.
events_[current_slot_].radius_x_ =
TuxelToPixelSize(input.value, x_num_tuxels_, x_num_pixels_) / 2.0f;
break;
case ABS_MT_TOUCH_MINOR:
altered_slots_.set(current_slot_);
events_[current_slot_].radius_y_ =
TuxelToPixelSize(input.value, y_num_tuxels_, y_num_pixels_) / 2.0f;
break;
case ABS_MT_POSITION_X:
altered_slots_.set(current_slot_);
events_[current_slot_].x_ = TuxelsToPixels(input.value,
x_min_tuxels_,
x_num_tuxels_,
x_min_pixels_,
x_num_pixels_);
break;
case ABS_MT_POSITION_Y:
altered_slots_.set(current_slot_);
events_[current_slot_].y_ = TuxelsToPixels(input.value,
y_min_tuxels_,
y_num_tuxels_,
y_min_pixels_,
y_num_pixels_);
break;
case ABS_MT_TRACKING_ID:
altered_slots_.set(current_slot_);
if (input.value < 0) {
events_[current_slot_].type_ = ET_TOUCH_RELEASED;
} else {
events_[current_slot_].finger_ = input.value;
events_[current_slot_].type_ = ET_TOUCH_PRESSED;
}
break;
case ABS_MT_PRESSURE:
altered_slots_.set(current_slot_);
events_[current_slot_].pressure_ = input.value - pressure_min_;
events_[current_slot_].pressure_ /= pressure_max_ - pressure_min_;
break;
case ABS_MT_SLOT:
current_slot_ = input.value;
altered_slots_.set(current_slot_);
break;
default:
DVLOG(5) << "unhandled code for EV_ABS: " << input.code;
}
}
void TouchEventConverterEvdev::ProcessSyn(const input_event& input) {
switch (input.code) {
case SYN_REPORT:
if (syn_dropped_) {
// Have to re-initialize.
if (Reinitialize()) {
syn_dropped_ = false;
altered_slots_.reset();
} else {
LOG(ERROR) << "failed to re-initialize device info";
}
} else {
ReportEvents(base::TimeDelta::FromMicroseconds(
input.time.tv_sec * 1000000 + input.time.tv_usec));
}
if (is_type_a_)
current_slot_ = 0;
break;
case SYN_MT_REPORT:
// For type A devices, we just get a stream of all current contacts,
// in some arbitrary order.
events_[current_slot_++].type_ = ET_TOUCH_PRESSED;
is_type_a_ = true;
break;
case SYN_DROPPED:
// Some buffer has overrun. We ignore all events up to and
// including the next SYN_REPORT.
syn_dropped_ = true;
break;
default:
NOTIMPLEMENTED() << "invalid code for EV_SYN: " << input.code;
}
}
void TouchEventConverterEvdev::ReportEvents(base::TimeDelta delta) {
for (int i = 0; i < MAX_FINGERS; i++) {
if (altered_slots_[i]) {
// TODO(rikroege): Support elliptical finger regions.
scoped_ptr<TouchEvent> event(
new TouchEvent(events_[i].type_,
gfx::PointF(events_[i].x_, events_[i].y_),
/* flags */ 0,
/* touch_id */ i,
delta,
/* radius_x */ events_[i].radius_x_,
/* radius_y */ events_[i].radius_y_,
/* angle */ 0.,
events_[i].pressure_));
event->set_source_device_id(id_);
callback_.Run(event.Pass());
// Subsequent events for this finger will be touch-move until it
// is released.
events_[i].type_ = ET_TOUCH_MOVED;
}
}
altered_slots_.reset();
}
} // namespace ui