blob: 83525345ea9d8af37dd4305a9a6f3b6bb7e200b1 [file] [log] [blame]
// Copyright 2021 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 "arc/data-snapshotd/esc_key_watcher.h"
#include <utility>
#include <fcntl.h>
#include <sys/epoll.h>
#include <base/files/file_enumerator.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
namespace arc {
namespace data_snapshotd {
namespace {
constexpr char kDevInputEvent[] = "/dev/input";
constexpr char kEventDevName[] = "*event*";
// Determines if the given |bit| is set in the |bitmask| array.
bool TestBit(const int bit, const uint8_t* bitmask) {
return (bitmask[bit / 8] >> (bit % 8)) & 1;
}
// Check if ESC key is supported by |fd|.
bool IsEscKeySupported(const int fd) {
uint8_t key_bitmask[KEY_MAX / 8 + 1];
if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bitmask)), key_bitmask) == -1) {
PLOG(ERROR) << "Failed to ioctl to determine supported key events";
return false;
}
return TestBit(KEY_ESC, key_bitmask);
}
} // namespace
EscKeyWatcher::EscKeyWatcher(Delegate* delegate) : delegate_(delegate) {
DCHECK(delegate_);
}
EscKeyWatcher::~EscKeyWatcher() = default;
bool EscKeyWatcher::Init() {
if (!GetValidFds()) {
LOG(ERROR) << "No valid input devices found.";
return false;
}
if (!EpollCreate(&epfd_)) {
PLOG(ERROR) << " EpollCreate failed, cannot watch epfd.";
return false;
}
watcher_ = base::FileDescriptorWatcher::WatchReadable(
epfd_.get(),
base::BindRepeating(&EscKeyWatcher::OnKeyEvent, base::Unretained(this)));
if (!watcher_) {
LOG(ERROR) << "Failed to watch epoll fd.";
return false;
}
return true;
}
bool EscKeyWatcher::EpollCreate(base::ScopedFD* epfd) {
*epfd = base::ScopedFD(epoll_create1(EPOLL_CLOEXEC));
if (epfd->get() < 0) {
PLOG(ERROR) << "Epoll_create failed";
return false;
}
for (int i = 0; i < fds_.size(); ++i) {
struct epoll_event ep_event {
.events = EPOLLIN, .data.u32 = static_cast<uint32_t>(i),
};
if (epoll_ctl(epfd->get(), EPOLL_CTL_ADD, fds_[i].get(), &ep_event) < 0) {
PLOG(ERROR) << "Epoll_ctl failed";
return false;
}
}
return true;
}
bool EscKeyWatcher::GetValidFds() {
fds_.clear();
base::FileEnumerator file_enumerator(base::FilePath(kDevInputEvent), true,
base::FileEnumerator::FILES,
kEventDevName);
for (base::FilePath dir_path = file_enumerator.Next(); !dir_path.empty();
dir_path = file_enumerator.Next()) {
base::ScopedFD fd(open(dir_path.value().c_str(), O_RDONLY | O_CLOEXEC));
if (!fd.is_valid()) {
continue;
}
if (IsEscKeySupported(fd.get()))
fds_.push_back(std::move(fd));
}
return !fds_.empty();
}
bool EscKeyWatcher::GetEpEvent(int epfd, struct input_event* ev, int* index) {
struct epoll_event ep_event;
if (epoll_wait(epfd, &ep_event, 1, -1) <= 0) {
PLOG(ERROR) << "epoll_wait failed";
return false;
}
*index = ep_event.data.u32;
if (read(fds_[*index].get(), ev, sizeof(*ev)) != sizeof(*ev)) {
PLOG(ERROR) << "Could not read event";
return false;
}
return true;
}
void EscKeyWatcher::OnKeyEvent() {
struct input_event ev;
int index = 0;
if (!GetEpEvent(epfd_.get(), &ev, &index)) {
PLOG(ERROR) << "Could not get event";
return;
}
// Notify only if the valid key is pressed.
if (ev.type != EV_KEY || ev.code > KEY_MAX)
return;
// Notify only about ESC key.
if (ev.code != KEY_ESC)
return;
delegate_->SendCancelSignal();
}
} // namespace data_snapshotd
} // namespace arc