Index: ui/events/ozone/evdev/touch_event_converter_evdev.cc |
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0c9be25f2b702d21ecd225220ddd0f57c844b56e |
--- /dev/null |
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc |
@@ -0,0 +1,442 @@ |
+// 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 "base/trace_event/trace_event.h" |
+#include "ui/events/devices/device_data_manager.h" |
+#include "ui/events/devices/device_util_linux.h" |
+#include "ui/events/event.h" |
+#include "ui/events/event_constants.h" |
+#include "ui/events/event_switches.h" |
+#include "ui/events/event_utils.h" |
+#include "ui/events/ozone/evdev/device_event_dispatcher_evdev.h" |
+#include "ui/events/ozone/evdev/touch_evdev_types.h" |
+#include "ui/events/ozone/evdev/touch_noise/touch_noise_finder.h" |
+ |
+namespace { |
+ |
+const int kMaxTrackingId = 0xffff; // TRKID_MAX in kernel. |
+ |
+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(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
+ switches::kTouchCalibration), |
+ ",", &parts) >= 4) { |
+ if (!base::StringToInt(parts[0], &cal->bezel_left)) |
+ LOG(ERROR) << "Incorrect left border calibration value passed."; |
+ if (!base::StringToInt(parts[1], &cal->bezel_right)) |
+ LOG(ERROR) << "Incorrect right border calibration value passed."; |
+ if (!base::StringToInt(parts[2], &cal->bezel_top)) |
+ LOG(ERROR) << "Incorrect top border calibration value passed."; |
+ if (!base::StringToInt(parts[3], &cal->bezel_bottom)) |
+ LOG(ERROR) << "Incorrect bottom border calibration value passed."; |
+ } |
+} |
+ |
+int32_t AbsCodeToMtCode(int32_t code) { |
+ switch (code) { |
+ case ABS_X: |
+ return ABS_MT_POSITION_X; |
+ case ABS_Y: |
+ return ABS_MT_POSITION_Y; |
+ case ABS_PRESSURE: |
+ return ABS_MT_PRESSURE; |
+ case ABS_DISTANCE: |
+ return ABS_MT_DISTANCE; |
+ default: |
+ return -1; |
+ } |
+} |
+ |
+const int kTrackingIdForUnusedSlot = -1; |
+ |
+} // namespace |
+ |
+namespace ui { |
+ |
+TouchEventConverterEvdev::TouchEventConverterEvdev( |
+ int fd, |
+ base::FilePath path, |
+ int id, |
+ InputDeviceType type, |
+ const EventDeviceInfo& devinfo, |
+ DeviceEventDispatcherEvdev* dispatcher) |
+ : EventConverterEvdev(fd, |
+ path, |
+ id, |
+ type, |
+ devinfo.name(), |
+ devinfo.vendor_id(), |
+ devinfo.product_id()), |
+ dispatcher_(dispatcher) { |
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kExtraTouchNoiseFiltering)) { |
+ touch_noise_finder_.reset(new TouchNoiseFinder); |
+ } |
+} |
+ |
+TouchEventConverterEvdev::~TouchEventConverterEvdev() { |
+} |
+ |
+void TouchEventConverterEvdev::Initialize(const EventDeviceInfo& info) { |
+ has_mt_ = info.HasMultitouch(); |
+ |
+ if (has_mt_) { |
+ 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; |
+ touch_points_ = |
+ std::min<int>(info.GetAbsMaximum(ABS_MT_SLOT) + 1, kNumTouchEvdevSlots); |
+ current_slot_ = info.GetAbsValue(ABS_MT_SLOT); |
+ } else { |
+ pressure_min_ = info.GetAbsMinimum(ABS_PRESSURE); |
+ pressure_max_ = info.GetAbsMaximum(ABS_PRESSURE); |
+ x_min_tuxels_ = info.GetAbsMinimum(ABS_X); |
+ x_num_tuxels_ = info.GetAbsMaximum(ABS_X) - x_min_tuxels_ + 1; |
+ y_min_tuxels_ = info.GetAbsMinimum(ABS_Y); |
+ y_num_tuxels_ = info.GetAbsMaximum(ABS_Y) - y_min_tuxels_ + 1; |
+ touch_points_ = 1; |
+ current_slot_ = 0; |
+ } |
+ |
+ quirk_left_mouse_button_ = |
+ !has_mt_ && !info.HasKeyEvent(BTN_TOUCH) && info.HasKeyEvent(BTN_LEFT); |
+ |
+ // Apply --touch-calibration. |
+ if (type() == INPUT_DEVICE_INTERNAL) { |
+ 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); |
+ } |
+ |
+ events_.resize(touch_points_); |
+ |
+ if (has_mt_) { |
+ for (size_t i = 0; i < events_.size(); ++i) { |
+ events_[i].x = info.GetAbsMtSlotValueWithDefault(ABS_MT_POSITION_X, i, 0); |
+ events_[i].y = info.GetAbsMtSlotValueWithDefault(ABS_MT_POSITION_Y, i, 0); |
+ events_[i].tracking_id = info.GetAbsMtSlotValueWithDefault( |
+ ABS_MT_TRACKING_ID, i, kTrackingIdForUnusedSlot); |
+ events_[i].touching = (events_[i].tracking_id >= 0); |
+ events_[i].slot = i; |
+ |
+ // Dirty the slot so we'll update the consumer at the first opportunity. |
+ // We can't dispatch here as this is currently called on the worker pool. |
+ // TODO(spang): Move initialization off worker pool. |
+ events_[i].altered = true; |
+ |
+ // Optional bits. |
+ events_[i].radius_x = |
+ info.GetAbsMtSlotValueWithDefault(ABS_MT_TOUCH_MAJOR, i, 0) / 2.0f; |
+ events_[i].radius_y = |
+ info.GetAbsMtSlotValueWithDefault(ABS_MT_TOUCH_MINOR, i, 0) / 2.0f; |
+ events_[i].pressure = ScalePressure( |
+ info.GetAbsMtSlotValueWithDefault(ABS_MT_PRESSURE, i, 0)); |
+ } |
+ } else { |
+ // TODO(spang): Add key state to EventDeviceInfo to allow initial contact. |
+ // (and make sure to take into account quirk_left_mouse_button_) |
+ events_[0].x = 0; |
+ events_[0].y = 0; |
+ events_[0].tracking_id = kTrackingIdForUnusedSlot; |
+ events_[0].touching = false; |
+ events_[0].slot = 0; |
+ events_[0].radius_x = 0; |
+ events_[0].radius_y = 0; |
+ events_[0].pressure = 0; |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::Reinitialize() { |
+ EventDeviceInfo info; |
+ if (!info.Initialize(fd_)) { |
+ LOG(ERROR) << "Failed to synchronize state for touch device: " |
+ << path_.value(); |
+ Stop(); |
+ return; |
+ } |
+ |
+ Initialize(info); |
+} |
+ |
+bool TouchEventConverterEvdev::HasTouchscreen() const { |
+ return true; |
+} |
+ |
+gfx::Size TouchEventConverterEvdev::GetTouchscreenSize() const { |
+ return gfx::Size(x_num_tuxels_, y_num_tuxels_); |
+} |
+ |
+int TouchEventConverterEvdev::GetTouchPoints() const { |
+ return touch_points_; |
+} |
+ |
+void TouchEventConverterEvdev::OnEnabled() { |
+ ReportEvents(EventTimeForNow()); |
+} |
+ |
+void TouchEventConverterEvdev::OnDisabled() { |
+ ReleaseTouches(); |
+} |
+ |
+void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) { |
+ TRACE_EVENT1("evdev", |
+ "TouchEventConverterEvdev::OnFileCanReadWithoutBlocking", "fd", |
+ fd); |
+ |
+ input_event inputs[kNumTouchEvdevSlots * 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; |
+ } |
+ |
+ if (!enabled_) { |
+ dropped_events_ = true; |
+ return; |
+ } |
+ |
+ for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) { |
+ if (!has_mt_) { |
+ // Emulate the device as an MT device with only 1 slot by inserting extra |
+ // MT protocol events in the stream. |
+ EmulateMultitouchEvent(inputs[i]); |
+ } |
+ |
+ ProcessMultitouchEvent(inputs[i]); |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::ProcessMultitouchEvent( |
+ const input_event& input) { |
+ if (input.type == EV_SYN) { |
+ ProcessSyn(input); |
+ } else if (dropped_events_) { |
+ // Do nothing. This branch indicates we have lost sync with the driver. |
+ } else if (input.type == EV_ABS) { |
+ if (events_.size() <= current_slot_) { |
+ LOG(ERROR) << "current_slot_ (" << current_slot_ |
+ << ") >= events_.size() (" << events_.size() << ")"; |
+ } else { |
+ ProcessAbs(input); |
+ } |
+ } else if (input.type == EV_KEY) { |
+ ProcessKey(input); |
+ } else if (input.type == EV_MSC) { |
+ // Ignored. |
+ } else { |
+ NOTIMPLEMENTED() << "invalid type: " << input.type; |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::EmulateMultitouchEvent( |
+ const input_event& event) { |
+ input_event emulated_event = event; |
+ |
+ if (event.type == EV_ABS) { |
+ emulated_event.code = AbsCodeToMtCode(event.code); |
+ if (emulated_event.code >= 0) |
+ ProcessMultitouchEvent(emulated_event); |
+ } else if (event.type == EV_KEY) { |
+ if (event.code == BTN_TOUCH || |
+ (quirk_left_mouse_button_ && event.code == BTN_LEFT)) { |
+ emulated_event.type = EV_ABS; |
+ emulated_event.code = ABS_MT_TRACKING_ID; |
+ emulated_event.value = |
+ event.value ? NextTrackingId() : kTrackingIdForUnusedSlot; |
+ ProcessMultitouchEvent(emulated_event); |
+ } |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::ProcessKey(const input_event& input) { |
+ switch (input.code) { |
+ case BTN_TOUCH: |
+ case BTN_LEFT: |
+ break; |
+ default: |
+ NOTIMPLEMENTED() << "invalid code for EV_KEY: " << input.code; |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::ProcessAbs(const input_event& input) { |
+ switch (input.code) { |
+ case ABS_MT_TOUCH_MAJOR: |
+ // 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 = input.value / 2.0f; |
+ break; |
+ case ABS_MT_TOUCH_MINOR: |
+ events_[current_slot_].radius_y = input.value / 2.0f; |
+ break; |
+ case ABS_MT_POSITION_X: |
+ events_[current_slot_].x = input.value; |
+ break; |
+ case ABS_MT_POSITION_Y: |
+ events_[current_slot_].y = input.value; |
+ break; |
+ case ABS_MT_TRACKING_ID: |
+ UpdateTrackingId(current_slot_, input.value); |
+ break; |
+ case ABS_MT_PRESSURE: |
+ events_[current_slot_].pressure = ScalePressure(input.value); |
+ break; |
+ case ABS_MT_SLOT: |
+ if (input.value >= 0 && |
+ static_cast<size_t>(input.value) < events_.size()) { |
+ current_slot_ = input.value; |
+ } else { |
+ LOG(ERROR) << "invalid touch event index: " << input.value; |
+ return; |
+ } |
+ break; |
+ default: |
+ DVLOG(5) << "unhandled code for EV_ABS: " << input.code; |
+ return; |
+ } |
+ events_[current_slot_].altered = true; |
+} |
+ |
+void TouchEventConverterEvdev::ProcessSyn(const input_event& input) { |
+ switch (input.code) { |
+ case SYN_REPORT: |
+ ReportEvents(EventConverterEvdev::TimeDeltaFromInputEvent(input)); |
+ break; |
+ case SYN_DROPPED: |
+ // Some buffer has overrun. We ignore all events up to and |
+ // including the next SYN_REPORT. |
+ dropped_events_ = true; |
+ break; |
+ default: |
+ NOTIMPLEMENTED() << "invalid code for EV_SYN: " << input.code; |
+ } |
+} |
+ |
+EventType TouchEventConverterEvdev::GetEventTypeForTouch( |
+ const InProgressTouchEvdev& touch) { |
+ if (touch.cancelled) |
+ return ET_UNKNOWN; |
+ |
+ if (touch_noise_finder_ && touch_noise_finder_->SlotHasNoise(touch.slot)) { |
+ if (touch.touching && !touch.was_touching) |
+ return ET_UNKNOWN; |
+ return ET_TOUCH_CANCELLED; |
+ } |
+ |
+ if (touch.touching) |
+ return touch.was_touching ? ET_TOUCH_MOVED : ET_TOUCH_PRESSED; |
+ return touch.was_touching ? ET_TOUCH_RELEASED : ET_UNKNOWN; |
+} |
+ |
+void TouchEventConverterEvdev::ReportEvent(const InProgressTouchEvdev& event, |
+ EventType event_type, |
+ const base::TimeDelta& timestamp) { |
+ dispatcher_->DispatchTouchEvent(TouchEventParams( |
+ input_device_.id, event.slot, event_type, gfx::PointF(event.x, event.y), |
+ gfx::Vector2dF(event.radius_x, event.radius_y), event.pressure, |
+ timestamp)); |
+} |
+ |
+void TouchEventConverterEvdev::ReportEvents(base::TimeDelta delta) { |
+ if (dropped_events_) { |
+ Reinitialize(); |
+ dropped_events_ = false; |
+ } |
+ |
+ if (touch_noise_finder_) |
+ touch_noise_finder_->HandleTouches(events_, delta); |
+ |
+ for (size_t i = 0; i < events_.size(); i++) { |
+ InProgressTouchEvdev* event = &events_[i]; |
+ if (!event->altered) |
+ continue; |
+ |
+ EventType event_type = GetEventTypeForTouch(*event); |
+ if (event_type == ET_UNKNOWN || event_type == ET_TOUCH_CANCELLED) |
+ event->cancelled = true; |
+ |
+ if (event_type != ET_UNKNOWN) |
+ ReportEvent(*event, event_type, delta); |
+ |
+ event->was_touching = event->touching; |
+ event->altered = false; |
+ } |
+} |
+ |
+void TouchEventConverterEvdev::UpdateTrackingId(int slot, int tracking_id) { |
+ InProgressTouchEvdev* event = &events_[slot]; |
+ |
+ if (event->tracking_id == tracking_id) |
+ return; |
+ |
+ event->tracking_id = tracking_id; |
+ event->touching = (tracking_id >= 0); |
+ event->altered = true; |
+ |
+ if (tracking_id >= 0) |
+ event->cancelled = false; |
+} |
+ |
+void TouchEventConverterEvdev::ReleaseTouches() { |
+ for (size_t slot = 0; slot < events_.size(); slot++) |
+ UpdateTrackingId(slot, kTrackingIdForUnusedSlot); |
+ |
+ ReportEvents(EventTimeForNow()); |
+} |
+ |
+float TouchEventConverterEvdev::ScalePressure(int32_t value) { |
+ float pressure = value - pressure_min_; |
+ if (pressure_max_ - pressure_min_) |
+ pressure /= pressure_max_ - pressure_min_; |
+ return pressure; |
+} |
+ |
+int TouchEventConverterEvdev::NextTrackingId() { |
+ return next_tracking_id_++ & kMaxTrackingId; |
+} |
+ |
+} // namespace ui |