blob: aad74d2a181e2f6dafebc043e71f63683ea16051 [file] [log] [blame]
// Copyright 2017 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 <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
namespace {
constexpr struct input_event kSync = {
.type = EV_SYN, .code = SYN_REPORT, .value = 0};
constexpr int kBitsPerInt = sizeof(uint32_t) * 8;
constexpr int kMaxInputDev = 256;
// When creating an input device, time delay before send out events to it.
constexpr base::TimeDelta kUinputDevInjectDelay =
base::TimeDelta::FromSeconds(1);
constexpr char kUinputDev[] = "/dev/uinput";
const int kMaxBit = std::max(std::max(EV_MAX, KEY_MAX), SW_MAX);
const int kMaxInt = (kMaxBit - 1) / kBitsPerInt + 1;
bool TestBit(const uint32_t bitmask[], int bit) {
return ((bitmask[bit / kBitsPerInt] >> (bit % kBitsPerInt)) & 1);
}
bool HasEventBit(int fd, int event_type, int bit) {
uint32_t bitmask[kMaxInt];
memset(bitmask, 0, sizeof(bitmask));
if (ioctl(fd, EVIOCGBIT(event_type, sizeof(bitmask)), bitmask) < 0)
return false;
return TestBit(bitmask, bit);
}
void LogErrorExit(const std::string& message) {
LOG(ERROR) << message;
exit(1);
}
// CreateEvent return a correctly filled input_event. It aborts on
// invalid |code| or |value|.
// |code|: "tablet", "lid"
// |value|: 0, 1
struct input_event CreateEvent(const std::string& code, int32_t value) {
struct input_event event;
event.type = EV_SW;
if (code == "tablet")
event.code = SW_TABLET_MODE;
else if (code == "lid")
event.code = SW_LID;
else
LogErrorExit("--code=<tablet|lid>");
if (value != 0 && value != 1)
LogErrorExit("--value=<0|1>");
event.value = value;
return event;
}
// Create an input device which supports given event |type|
// and |code| with uinput interface. It will be alive for
// |lifetime|.
base::ScopedFD CreateDevice(int type, int code, base::TimeDelta lifetime) {
base::ScopedFD fd(open(kUinputDev, O_RDWR | O_CLOEXEC));
if (!fd.is_valid()) {
PLOG(ERROR) << "Failed to open " << kUinputDev;
return base::ScopedFD();
}
int ui_set_type_bit;
switch (type) {
case EV_SW:
ui_set_type_bit = UI_SET_SWBIT;
break;
case EV_KEY:
ui_set_type_bit = UI_SET_KEYBIT;
break;
default:
LOG(FATAL) << "not handled type: " << type;
}
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), UI_SET_EVBIT, type))) {
PLOG(ERROR) << "ioctl ui_set_evbit";
return base::ScopedFD();
}
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), ui_set_type_bit, code))) {
PLOG(ERROR) << "ioctl ui_set_type_bit";
return base::ScopedFD();
}
struct uinput_user_dev dev;
memset(&dev, 0, sizeof(dev));
snprintf(dev.name, sizeof(dev.name), "inject powerd");
TEMP_FAILURE_RETRY(write(fd.get(), &dev, sizeof(dev)));
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), UI_DEV_CREATE))) {
PLOG(ERROR) << "ioctl ui_dev_create";
return base::ScopedFD();
}
// Create a child process to hold this fd to keep created
// device alive.
if (fork() == 0) {
setsid();
sleep(lifetime.InSeconds());
exit(0);
}
// Give powerd time to open new created device.
sleep(kUinputDevInjectDelay.InSeconds());
return fd;
}
// Find the event device which supports said |type| and |code| and
// return opened file descriptor to it.
base::ScopedFD OpenDevice(int type, int code) {
base::ScopedFD fd;
for (int i = 0; i < kMaxInputDev; ++i) {
fd.reset(open(base::StringPrintf("/dev/input/event%d", i).c_str(),
O_RDWR | O_CLOEXEC));
if (!fd.is_valid())
break;
if (HasEventBit(fd.get(), 0, type) && HasEventBit(fd.get(), type, code))
break;
}
return fd;
}
void InjectEvent(const struct input_event& event,
bool create_dev,
base::TimeDelta lifetime) {
base::ScopedFD fd = OpenDevice(event.type, event.code);
if (!fd.is_valid()) {
if (!create_dev) {
LogErrorExit("No supported input device, try --create_dev");
}
fd = CreateDevice(event.type, event.code, lifetime);
if (!fd.is_valid()) {
LogErrorExit("Failed to create device");
}
}
TEMP_FAILURE_RETRY(write(fd.get(), &event, sizeof(event)));
TEMP_FAILURE_RETRY(write(fd.get(), &kSync, sizeof(kSync)));
}
} // namespace
int main(int argc, char* argv[]) {
DEFINE_string(code, "", "Input event type to inject (one of tablet, lid)");
DEFINE_int32(value, -1, "Input event value to inject (0 is off, 1 is on)");
DEFINE_bool(create_dev, false,
"Create device if no device supports wanted input event");
DEFINE_int32(dev_lifetime, 300, "Lifetime (in seconds) of created device");
brillo::FlagHelper::Init(argc, argv, "Inject input events to powerd.\n");
struct input_event event = CreateEvent(FLAGS_code, FLAGS_value);
InjectEvent(event, FLAGS_create_dev,
base::TimeDelta::FromSeconds(FLAGS_dev_lifetime));
return 0;
}