blob: e71f35aa210f9bb62345688bbb5a6d2c36ed1caf [file] [log] [blame]
// Copyright 2016 The Chromium OS 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 "touch_keyboard/faketouchpad.h"
namespace touch_keyboard {
FakeTouchpad::FakeTouchpad(int xmin, int xmax, int ymin, int ymax,
unsigned char axis_inversion_flags) :
xmin_(xmin), xmax_(xmax), ymin_(ymin), ymax_(ymax) {
invertx_ = static_cast<bool>(axis_inversion_flags & kInvertX);
inverty_ = static_cast<bool>(axis_inversion_flags & kInvertY);
for (int i = 0; i < mtstatemachine::kNumSlots; i++) {
slot_memberships_.push_back(false);
}
}
void FakeTouchpad::Start(std::string const &source_device_path,
std::string const &touchpad_device_name) {
OpenSourceDevice(source_device_path);
CreateUinputFD();
// Enable the few button events that touchpads need.
EnableEventType(EV_KEY);
EnableKeyEvent(BTN_TOUCH);
EnableKeyEvent(BTN_TOOL_FINGER);
EnableKeyEvent(BTN_TOOL_DOUBLETAP);
EnableKeyEvent(BTN_TOOL_TRIPLETAP);
EnableKeyEvent(BTN_TOOL_QUADTAP);
// Duplicate the ABS events from the source device.
CopyABSOutputEvents(source_fd_, xmax_ - xmin_, ymax_ - ymin_);
// Finally, tell kernel to create the new fake touchpad's uinput device.
FinalizeUinputCreation(touchpad_device_name);
// Loop forever consuming the events coming in from the source device.
Consume();
}
void FakeTouchpad::Consume() {
while (1) {
struct input_event ev;
bool event_ready = GetNextEvent(kNoTimeout, &ev);
// If we timed out waiting, then there is no event yet.
if (!event_ready) {
continue;
}
if (sm_.AddEvent(ev, NULL)) {
// Sync over all the touch events from the source state machine.
int touch_count = SyncTouchEvents();
// Make sure the BTN events are correct since this is a fake touchpad.
SendTouchpadBtnEvents(touch_count);
// Finally send a SYN after all applicable events are sent.
SendEvent(EV_SYN, SYN_REPORT, 0);
}
}
}
void FakeTouchpad::SendTouchpadBtnEvents(int touch_count) const {
// Since this is a fake touchpad, we need to send BTN_TOUCH and BTN_TOOL_*
// events whenever a finger arrives and leaves for the gesture library to
// interpret the motions correctly. This function generates those events
// based on the number of fingers currently being reported by the fake
// touchpad.
SendEvent(EV_KEY, BTN_TOUCH, (touch_count > 0) ? 1 : 0);
SendEvent(EV_KEY, BTN_TOOL_FINGER, (touch_count == 1) ? 1 : 0);
SendEvent(EV_KEY, BTN_TOOL_DOUBLETAP, (touch_count == 2) ? 1 : 0);
SendEvent(EV_KEY, BTN_TOOL_TRIPLETAP, (touch_count == 3) ? 1 : 0);
SendEvent(EV_KEY, BTN_TOOL_QUADTAP, (touch_count == 4) ? 1 : 0);
}
bool FakeTouchpad::Contains(mtstatemachine::Slot const &slot) const {
// Check and see if the contact in the slot is currently contained within
// the boundaries of this region.
int x = slot.FindValueByEvent(EV_ABS, ABS_MT_POSITION_X);
int y = slot.FindValueByEvent(EV_ABS, ABS_MT_POSITION_Y);
if (x < xmin_ || x > xmax_)
return false;
if (y < ymin_ || y > ymax_)
return false;
return true;
}
bool FakeTouchpad::PassEventsThrough(mtstatemachine::Slot const &slot) const {
// Go through the slot in question and send events setting each of the set
// values into this region. Essentially this updates all of the values for
// this slot in the kernel to match our internal version.
// This function returns True if the updated slot represented a contact that
// is current on the touchpad.
mtstatemachine::Slot::const_iterator it;
bool is_valid = false;
// Iterate over each value in the slot and send the corresponding event.
for (it = slot.begin(); it != slot.end(); it++) {
mtstatemachine::EventKey slot_event_key = it->first;
int value = it->second;
// If the value is for an unsupported event_key, skip it.
if (!slot_event_key.IsSupportedForTouchpads())
continue;
// Check the tracking ID. (-1 indicates this contact is not valid anymore)
if (slot_event_key.IsTrackingID()) {
is_valid |= (value != -1);
}
// Transform X and Y values to keep the corner of the region 0,0 and
// invert any axes that were set to be inverted at creation.
if (slot_event_key.IsX()) {
value -= xmin_;
if (invertx_) {
value = (xmax_ - xmin_) - value;
}
} else if (slot_event_key.IsY()) {
value -= ymin_;
if (inverty_) {
value = (ymax_ - ymin_) - value;
}
}
// Push an event that sets this value into the region.
SendEvent(slot_event_key.type_, slot_event_key.code_, value);
}
return is_valid;
}
int FakeTouchpad::SyncTouchEvents() {
// Scan through all the slots of the state machine and sync the
// uinput device with it by copying over the touch events for any contacts
// that are currently contained within the region, returning the number
// of such contacts. This only passes on events that are contained within
// the region and performs transformations on the coordinates to maintain
// the illusion of a different device (shifting x/y, adding fake finger
// arriving events, etc)
int touch_count = 0;
for (int slot = 0; slot < mtstatemachine::kNumSlots; slot++) {
// Send a SLOT message to make sure these events go to the right slot
SendEvent(EV_ABS, ABS_MT_SLOT, slot);
// Don't pass on events from contacts outside of the region.
if (!Contains(sm_.slots_[slot])) {
// If this slot just left the region, send a finger-leaving event.
if (slot_memberships_[slot]) {
SendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
}
slot_memberships_[slot] = false;
continue;
} else {
// If this slot just entered the region, send a finger-arrive event.
if (!slot_memberships_[slot]) {
int tid = sm_.slots_[slot].FindValueByEvent(EV_ABS,
ABS_MT_TRACKING_ID);
SendEvent(EV_ABS, ABS_MT_TRACKING_ID, tid);
}
slot_memberships_[slot] = true;
}
// Scan through the slot and update all the properties.
bool valid_finger = PassEventsThrough(sm_.slots_[slot]);
if (valid_finger && slot_memberships_[slot]) {
touch_count++;
}
}
return touch_count;
}
} // namespace touch_keyboard