#include "ui/events/ozone/evdev/libinput_event_converter.h"
#include <fcntl.h>
#include <string>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
#include "ui/events/ozone/evdev/device_event_dispatcher_evdev.h"
#include "ui/events/ozone/evdev/event_device_info.h"
#include "ui/events/ozone/evdev/input_device_settings_evdev.h"
namespace ui {
namespace {
double GetAxisValue(libinput_event_pointer* const event,
const libinput_pointer_axis axis) {
if (!libinput_event_pointer_has_axis(event, axis)) {
return 0.0;
}
return -libinput_event_pointer_get_axis_value(event, axis);
}
template <typename Value>
void LogConfigStatus(const libinput_config_status status,
const char* key,
const Value value) {
switch (status) {
case LIBINPUT_CONFIG_STATUS_SUCCESS:
DVLOG(3) << "libinput config: set " << key << " to " << value;
break;
case LIBINPUT_CONFIG_STATUS_UNSUPPORTED:
LOG(ERROR) << "libinput config: " << key << " not supported";
break;
case LIBINPUT_CONFIG_STATUS_INVALID:
LOG(ERROR) << "libinput config: invalid parameter for " << key << ": "
<< value;
break;
}
}
}
LibInputEventConverter::LibInputEvent::LibInputEvent(LibInputEvent&& other)
: event_(other.event_) {
other.event_ = nullptr;
}
LibInputEventConverter::LibInputEvent::LibInputEvent(
libinput_event* const event)
: event_(event) {}
LibInputEventConverter::LibInputEvent::~LibInputEvent() {
if (event_) {
libinput_event_destroy(event_);
}
}
libinput_event_pointer* LibInputEventConverter::LibInputEvent::PointerEvent()
const {
auto* const event = libinput_event_get_pointer_event(event_);
DCHECK(event);
return event;
}
libinput_event_type LibInputEventConverter::LibInputEvent::Type() const {
return libinput_event_get_type(event_);
}
LibInputEventConverter::LibInputDevice::LibInputDevice(
int id,
libinput_device* const device)
: device_id_(id), device_(device) {
DCHECK(device);
libinput_device_ref(device);
DVLOG(2) << "device: \"" << libinput_device_get_name(device_)
<< "\", capabilities: " << GetCapabilitiesString();
}
LibInputEventConverter::LibInputDevice::LibInputDevice(LibInputDevice&& other)
: device_id_(other.device_id_), device_(other.device_) {
other.device_ = nullptr;
}
LibInputEventConverter::LibInputDevice::~LibInputDevice() {
if (device_) {
libinput_device_unref(device_);
}
}
void LibInputEventConverter::LibInputDevice::ApplySettings(
const InputDeviceSettingsEvdev& settings) const {
const auto& touchpad_settings = settings.GetTouchpadSettings(device_id_);
SetNaturalScrollEnabled(touchpad_settings.natural_scroll_enabled);
SetSensitivity(touchpad_settings.sensitivity);
SetTapToClickEnabled(touchpad_settings.tap_to_click_enabled);
}
std::string LibInputEventConverter::LibInputDevice::GetCapabilitiesString() {
std::vector<std::pair<libinput_device_capability, std::string>> caps = {
{LIBINPUT_DEVICE_CAP_KEYBOARD, "keyboard"},
{LIBINPUT_DEVICE_CAP_POINTER, "pointer"},
{LIBINPUT_DEVICE_CAP_TOUCH, "touch"},
{LIBINPUT_DEVICE_CAP_TABLET_TOOL, "tablet-tool"},
{LIBINPUT_DEVICE_CAP_TABLET_PAD, "tablet-pad"},
{LIBINPUT_DEVICE_CAP_GESTURE, "gesture"},
{LIBINPUT_DEVICE_CAP_SWITCH, "switch"}};
std::vector<std::string> parts(caps.size());
for (const auto& pair : caps) {
if (libinput_device_has_capability(device_, pair.first)) {
parts.emplace_back(pair.second);
}
}
return base::JoinString(parts, ", ");
}
void LibInputEventConverter::LibInputDevice::SetNaturalScrollEnabled(
const bool enabled) const {
const auto status = libinput_device_config_scroll_set_natural_scroll_enabled(
device_, enabled);
LogConfigStatus(status, "natural-scroll", enabled);
}
void LibInputEventConverter::LibInputDevice::SetSensitivity(
const int sensitivity) const {
const double speed = (sensitivity - 3.0) / 2.0;
const auto status = libinput_device_config_accel_set_speed(device_, speed);
LogConfigStatus(status, "sensitivity", speed);
}
void LibInputEventConverter::LibInputDevice::SetTapToClickEnabled(
const bool enabled) const {
const auto arg =
(enabled ? LIBINPUT_CONFIG_TAP_ENABLED : LIBINPUT_CONFIG_TAP_DISABLED);
const auto status = libinput_device_config_tap_set_enabled(device_, arg);
LogConfigStatus(status, "tap-to-click", arg);
}
LibInputEventConverter::LibInputContext::LibInputContext(
LibInputContext&& other)
: li_(other.li_) {
other.li_ = nullptr;
}
LibInputEventConverter::LibInputContext::LibInputContext(libinput* const li)
: li_(li) {
DCHECK(li_);
libinput_log_set_handler(li_, LogHandler);
libinput_log_set_priority(li_, LIBINPUT_LOG_PRIORITY_DEBUG);
}
LibInputEventConverter::LibInputContext::~LibInputContext() {
if (li_) {
libinput_unref(li_);
}
}
absl::optional<LibInputEventConverter::LibInputContext>
LibInputEventConverter::LibInputContext::Create() {
libinput* const li = libinput_path_create_context(&interface_, nullptr);
if (!li) {
LOG(ERROR) << "libinput_path_create_context failed";
return absl::nullopt;
}
return absl::make_optional(LibInputEventConverter::LibInputContext(li));
}
absl::optional<LibInputEventConverter::LibInputDevice>
LibInputEventConverter::LibInputContext::AddDevice(
int id,
const base::FilePath& path) const {
auto* const dev = libinput_path_add_device(li_, path.value().c_str());
if (!dev) {
LOG(ERROR) << "libinput_path_add_device failed with device: " << path;
return absl::nullopt;
}
return absl::make_optional(LibInputDevice(id, dev));
}
bool LibInputEventConverter::LibInputContext::Dispatch() const {
const auto rc = libinput_dispatch(li_);
if (rc != 0) {
LOG(ERROR) << "libinput_dispatch failed: " << rc;
return false;
}
return true;
}
int LibInputEventConverter::LibInputContext::Fd() {
return libinput_get_fd(li_);
}
absl::optional<LibInputEventConverter::LibInputEvent>
LibInputEventConverter::LibInputContext::NextEvent() const {
libinput_event* const event = libinput_get_event(li_);
if (!event) {
return absl::nullopt;
}
return absl::make_optional(LibInputEvent(event));
}
int LibInputEventConverter::LibInputContext::OpenRestricted(const char* path,
int flags,
void* user_data) {
VLOG(1) << "Open input: " << path;
int fd = open(path, flags);
return fd;
}
void LibInputEventConverter::LibInputContext::CloseRestricted(int fd,
void* user_data) {
close(fd);
}
void LibInputEventConverter::LibInputContext::LogHandler(
libinput* libinput,
enum libinput_log_priority priority,
const char* format,
va_list args) {
switch (priority) {
case LIBINPUT_LOG_PRIORITY_DEBUG:
VLOG(4) << "libinput: " << base::StringPrintV(format, args);
break;
case LIBINPUT_LOG_PRIORITY_INFO:
VLOG(1) << "libinput: " << base::StringPrintV(format, args);
break;
case LIBINPUT_LOG_PRIORITY_ERROR:
LOG(ERROR) << "libinput: " << base::StringPrintV(format, args);
break;
}
}
constexpr libinput_interface
LibInputEventConverter::LibInputContext::interface_;
std::unique_ptr<LibInputEventConverter> LibInputEventConverter::Create(
const base::FilePath& path,
int id,
const EventDeviceInfo& devinfo,
CursorDelegateEvdev* cursor,
DeviceEventDispatcherEvdev* dispatcher) {
auto context = LibInputContext::Create();
if (!context) {
LOG(ERROR) << "LibInputContext::Create failed";
return nullptr;
}
return std::make_unique<LibInputEventConverter>(
std::move(context.value()), path, id, devinfo, cursor, dispatcher);
}
LibInputEventConverter::LibInputEventConverter(
LibInputEventConverter::LibInputContext&& ctx,
const base::FilePath& path,
int id,
const EventDeviceInfo& devinfo,
CursorDelegateEvdev* cursor,
DeviceEventDispatcherEvdev* dispatcher)
: EventConverterEvdev(ctx.Fd(),
path,
id,
devinfo.device_type(),
devinfo.name(),
devinfo.phys(),
devinfo.vendor_id(),
devinfo.product_id(),
devinfo.version()),
dispatcher_(dispatcher),
cursor_(cursor),
has_keyboard_(devinfo.HasKeyboard()),
has_mouse_(devinfo.HasMouse()),
has_touchpad_(devinfo.HasTouchpad()),
has_touchscreen_(devinfo.HasTouchscreen()),
context_(std::move(ctx)),
device_(context_.AddDevice(id, path)) {}
LibInputEventConverter::~LibInputEventConverter() {}
void LibInputEventConverter::ApplyDeviceSettings(
const InputDeviceSettingsEvdev& settings) {
if (device_) {
device_->ApplySettings(settings);
} else {
LOG(ERROR)
<< "Unable to apply settings due to libinput_path_add_device failure";
}
}
bool LibInputEventConverter::HasKeyboard() const {
return has_keyboard_;
}
bool LibInputEventConverter::HasMouse() const {
return has_mouse_;
}
bool LibInputEventConverter::HasTouchpad() const {
return has_touchpad_;
}
bool LibInputEventConverter::HasTouchscreen() const {
return has_touchscreen_;
}
void LibInputEventConverter::OnFileCanReadWithoutBlocking(int fd) {
if (!context_.Dispatch()) {
LOG(ERROR) << "LibInputContext::Dispatch failed";
return;
}
while (auto event = context_.NextEvent()) {
HandleEvent(*event);
}
}
void LibInputEventConverter::HandleEvent(const LibInputEvent& event) {
switch (event.Type()) {
case LIBINPUT_EVENT_POINTER_MOTION:
HandlePointerMotion(event);
break;
case LIBINPUT_EVENT_POINTER_BUTTON:
HandlePointerButton(event);
break;
case LIBINPUT_EVENT_POINTER_AXIS:
HandlePointerAxis(event);
break;
case LIBINPUT_EVENT_TOUCH_DOWN:
case LIBINPUT_EVENT_TOUCH_UP:
case LIBINPUT_EVENT_TOUCH_MOTION:
case LIBINPUT_EVENT_TOUCH_CANCEL:
case LIBINPUT_EVENT_TOUCH_FRAME:
case LIBINPUT_EVENT_NONE:
case LIBINPUT_EVENT_DEVICE_ADDED:
case LIBINPUT_EVENT_DEVICE_REMOVED:
case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
case LIBINPUT_EVENT_TABLET_TOOL_TIP:
case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
case LIBINPUT_EVENT_TABLET_PAD_RING:
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
case LIBINPUT_EVENT_KEYBOARD_KEY:
case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
case LIBINPUT_EVENT_GESTURE_SWIPE_END:
case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
case LIBINPUT_EVENT_GESTURE_PINCH_END:
case LIBINPUT_EVENT_SWITCH_TOGGLE:
case LIBINPUT_EVENT_TABLET_PAD_KEY:
DVLOG(3) << "Ignoring libinput event: " << event.Type();
break;
}
}
void LibInputEventConverter::HandlePointerMotion(const LibInputEvent& evt) {
libinput_event_pointer* event = evt.PointerEvent();
const int flags = EF_NONE;
const double dx = libinput_event_pointer_get_dx(event);
const double dy = libinput_event_pointer_get_dy(event);
cursor_->MoveCursor(gfx::Vector2dF(dx, dy));
DVLOG(3) << "Pointer motion: dx=" << dx << ", dy=" << dy;
dispatcher_->DispatchMouseMoveEvent(
{input_device_.id, flags, cursor_->GetLocation(), nullptr ,
PointerDetails(EventPointerType::kMouse), Timestamp(evt)});
}
void LibInputEventConverter::HandlePointerButton(const LibInputEvent& evt) {
libinput_event_pointer* event = evt.PointerEvent();
const int flags = EF_NONE;
const uint32_t button = libinput_event_pointer_get_button(event);
const bool down = libinput_event_pointer_get_button_state(event) ==
LIBINPUT_BUTTON_STATE_PRESSED;
const MouseButtonMapType allow_remap = MouseButtonMapType::kNone;
DVLOG(3) << "Button: " << button << ", pressed: " << down;
dispatcher_->DispatchMouseButtonEvent(
{input_device_.id, flags, cursor_->GetLocation(), button, down,
allow_remap, PointerDetails(EventPointerType::kMouse), Timestamp(evt)});
}
void LibInputEventConverter::HandlePointerAxis(const LibInputEvent& evt) {
libinput_event_pointer* event = evt.PointerEvent();
const auto h = GetAxisValue(event, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL);
const auto v = GetAxisValue(event, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
const gfx::Vector2d delta(h, v);
DVLOG(3) << "Pointer axis h:" << h << ", v:" << v;
dispatcher_->DispatchScrollEvent({input_device_.id, ET_SCROLL,
cursor_->GetLocation(), delta, delta, 2,
Timestamp(evt)});
}
base::TimeTicks LibInputEventConverter::Timestamp(const LibInputEvent& evt) {
libinput_event_pointer* event = evt.PointerEvent();
uint64_t time_usec = libinput_event_pointer_get_time_usec(event);
return base::TimeTicks() + base::Microseconds(time_usec);
}
}