blob: 52285195f5e156c2a78ecf637e2c82a82d7a2378 [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/uinputdevice.h"
namespace touch_keyboard {
// When creating a new uinput device, you must specify these parameters like
// with an actual, physical device. These are sane, safe values that we use
// when creating a uinput device.
constexpr int kGoogleVendorID = 0x18d1;
constexpr int kDummyProductID = 0x00FF;
constexpr int kVersionNumber = 1;
// This is used when interpreting the results of ioctls that query the event
// capabilities of a device. They are returned in an int64_t bitfield.
constexpr int kNumBitsPerInt = sizeof(int64_t) * 8;
UinputDevice::~UinputDevice() {
// Tell the OS to destroy the uinput device as this object is destructed.
if (uinput_fd_ >= 0) {
int error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_DESTROY);
if (error) {
PLOG(ERROR) << "Unable to destroy uinput device (" << error << ")";
}
}
}
bool UinputDevice::CreateUinputFD() {
// Open a control file descriptor for creating a new uinput device.
// This file descriptor is used with ioctls to configure the device and
// receive the outgoing event information.
if (uinput_fd_ >= 0) {
LOG(ERROR) << "Control FD already opened! (" << uinput_fd_ << ") Quitting.";
return false;
}
uinput_fd_ = syscall_handler_->open(kUinputControlFilename,
O_WRONLY | O_NONBLOCK);
if (uinput_fd_ < 0) {
PLOG(ERROR) << "Unable to open " << kUinputControlFilename <<
" (" << uinput_fd_ << ")";
return false;
}
LOG(INFO) << "Uinput control file descriptor opened (" << uinput_fd_ << ")";
return true;
}
bool UinputDevice::EnableEventType(int ev_type) const {
// Tell the kernel that this uinput device will report events of a
// certain type (ABS, KEY, etc). Individual event codes must still be
// enabled individually, but their overarching types need to be enabled
// first, which is done here.
int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_EVBIT, ev_type);
if (error) {
LOG(ERROR) << "Unable to enable event type 0x" << std::hex << ev_type <<
"(" << std::dec << error << ")";
return false;
}
LOG(INFO) << "Enabled events of type 0x" << std::hex << ev_type;
return true;
}
bool UinputDevice::EnableKeyEvent(int ev_code) const {
// Tell the kernel that this region's uinput device will report a specific
// key event. (eg: KEY_BACKSPACE or BTN_TOUCH)
int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_KEYBIT, ev_code);
if (error) {
LOG(ERROR) << "Unable to enable EV_KEY 0x" << std::hex << ev_code <<
" events (" << std::dec << ")";
return false;
}
LOG(INFO) << "Enabled EV_KEY 0x" << std::hex << ev_code << " events";
return true;
}
bool UinputDevice::EnableAbsEvent(int ev_code) const {
// Tell the kernel that this region's uinput device will report a specific
// kind of ABS event. (eg: ABS_MT_POSITION_X or ABS_PRESSURE)
int error = syscall_handler_->ioctl(uinput_fd_, UI_SET_ABSBIT, ev_code);
if (error) {
LOG(ERROR) << "Unable to enable EV_ABS 0x" << std::hex << ev_code <<
" events (" << std::dec << error << ")";
return false;
}
LOG(INFO) << "Enabled EV_ABS 0x" << std::hex << ev_code << " events";
return true;
}
bool UinputDevice::CopyABSOutputEvents(int source_evdev_fd,
int width, int height) const {
// Configure this region's uinput device to report the correct kinds of
// events by copying the events that are reported by the input device
// who's file descriptor is passed as a reference.
// Instead of copying the range of the absolute axes though, the user
// specifies the width and height manually -- essentially creating a
// cloned input device with a different size than the source device.
int ev_code, error;
struct uinput_abs_setup abs_setup;
int64_t supported_abs_event_codes[((KEY_MAX - 1) / kNumBitsPerInt) + 1];
int64_t supported_event_types[EV_MAX];
// Query the source evdev file descriptor to see which event types it
// supports to make sure it supports ABS.
memset(supported_event_types, 0, sizeof(supported_event_types));
syscall_handler_->ioctl(source_evdev_fd, EVIOCGBIT(0, EV_MAX),
supported_event_types);
if (!IsEventSupported(EV_ABS, supported_event_types)) {
LOG(ERROR) << "Touchscreen does not support EV_ABS events.";
return false;
}
// Enable the EV_ABS event type for this device. Fail if it can't.
if (!EnableEventType(EV_ABS)) {
return false;
}
// Query the device to find which ABS event codes are supported and then
// enable them for this uinput device as well.
memset(supported_abs_event_codes, 0, sizeof(supported_abs_event_codes));
syscall_handler_->ioctl(source_evdev_fd, EVIOCGBIT(EV_ABS, KEY_MAX),
supported_abs_event_codes);
for (ev_code = 0; ev_code < KEY_MAX; ev_code++) {
// Skip over any event codes that are not supported.
if (!IsEventSupported(ev_code, supported_abs_event_codes)) {
continue;
}
// Enable this event code for the uinput device.
if (!EnableAbsEvent(ev_code)) {
return false;
}
// Fill in the ranges for each EV_ABS axis, modifying them for X and Y.
memset(&abs_setup, 0, sizeof(abs_setup));
abs_setup.code = ev_code;
syscall_handler_->ioctl(source_evdev_fd, EVIOCGABS(ev_code),
&abs_setup.absinfo);
if (ev_code == ABS_MT_POSITION_X || ev_code == ABS_X) {
abs_setup.absinfo.minimum = 0;
abs_setup.absinfo.maximum = width;
} else if (ev_code == ABS_MT_POSITION_Y || ev_code == ABS_Y) {
abs_setup.absinfo.minimum = 0;
abs_setup.absinfo.maximum = height;
}
error = syscall_handler_->ioctl(uinput_fd_, UI_ABS_SETUP, &abs_setup);
if (error) {
LOG(ERROR) << "Unable to set up axis for event code 0x" << std::hex <<
ev_code << " (" << std::dec << error << ")";
return false;
}
}
LOG(INFO) << "Successfully copied all EV_ABS events from source device";
return true;
}
bool UinputDevice::FinalizeUinputCreation(
std::string const &device_name) const {
int error;
struct uinput_setup device_info;
// Build a uinput device struct and write it to the ui_fd to specify the
// various identification parameters required such as the device name.
memset(&device_info, 0, sizeof(device_info));
snprintf(device_info.name, UINPUT_MAX_NAME_SIZE, "%s", device_name.c_str());
device_info.id.bustype = BUS_USB;
device_info.id.vendor = kGoogleVendorID;
device_info.id.product = kDummyProductID;
device_info.id.version = kVersionNumber;
error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_SETUP, &device_info);
if (error) {
LOG(ERROR) << "uinput device setup ioctl failed. (" << error << ")";
return false;
}
// Finally request that a new uinput device is created to those specs.
// After this step the device should be fully functional and ready to
// send events.
error = syscall_handler_->ioctl(uinput_fd_, UI_DEV_CREATE);
if (error) {
LOG(ERROR) << "uinput device creation ioctl failed. (" << error << ")";
return false;
}
LOG(INFO) << "Successfully finalized uinput device creation.";
return true;
}
bool UinputDevice::SendEvent(int ev_type, int ev_code, int value) const {
// Send an input event to the kernel through this uinput device.
struct input_event ev;
ev.type = ev_type;
ev.code = ev_code;
ev.value = value;
int bytes_written =
syscall_handler_->write(uinput_fd_, &ev, sizeof(struct input_event));
if (bytes_written != sizeof(struct input_event)) {
LOG(ERROR) << "Failed to write() when sending an event. (" <<
bytes_written << ")";
return false;
}
return true;
}
bool UinputDevice::IsEventSupported(int event,
int64_t *supported_event_types) const {
// The array is essentially a big bit field and we're testing to see if
// the event-th bit is set. That tells us if the event type in question
// is included in the support event types.
return (supported_event_types[event / kNumBitsPerInt] >>
(event % kNumBitsPerInt)) & 1;
}
} // namespace touch_keyboard