blob: c508bd2176c0588d0286382a3ee6961e9e8d5834 [file] [log] [blame]
// Copyright (c) 2012 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 "power_manager/powerd/system/input.h"
#include <dirent.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/vt.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <base/file_util.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "power_manager/common/power_constants.h"
#include "power_manager/common/prefs.h"
#include "power_manager/common/util.h"
#include "power_manager/powerd/system/input_observer.h"
#include "power_manager/powerd/system/udev.h"
using std::map;
using std::string;
using std::vector;
namespace power_manager {
namespace system {
namespace {
const char kSysClassInputPath[] = "/sys/class/input";
const char kDevInputPath[] = "/dev/input";
const char kEventBaseName[] = "event";
const char kInputBaseName[] = "input";
const char kWakeupDisabled[] = "disabled";
const char kWakeupEnabled[] = "enabled";
const char kInputMatchPattern[] = "input*";
const char kUsbMatchString[] = "usb";
const char kBluetoothMatchString[] = "bluetooth";
// Path to the console device where VT_GETSTATE ioctls are made to get the
// currently-active VT.
const char kConsolePath[] = "/dev/tty0";
// Path to an optional script used to disable touch devices when the lid is
// closed.
const char kTouchControlPath[] = "/opt/google/touch/touch-control.sh";
// Physical location (as returned by EVIOCGPHYS()) of power button devices that
// should be skipped.
//
// Skip input events from the ACPI power button (identified as LNXPWRBN) if a
// new power button is present on the keyboard.
const char kPowerButtonToSkip[] = "LNXPWRBN";
// Skip input events that are on the built-in keyboard if a legacy power button
// is used. Many of these devices advertise a power button but do not physically
// have one. Skipping will reduce the wasteful waking of powerd due to keyboard
// events.
const char kPowerButtonToSkipForLegacy[] = "isa";
// Given a string |name| consisting of |base_name| followed by a base-10
// integer, extracts the integer to |suffix|. Returns false if |name| didn't
// match the expected format.
bool GetSuffixNumber(const std::string& name,
const std::string& base_name,
int* suffix) {
size_t base_len = base_name.size();
if (name.substr(0, base_len) != base_name)
return false;
return base::StringToInt(name.substr(base_len, name.size() - base_len),
suffix);
}
} // namespace
const char Input::kInputUdevSubsystem[] = "input";
// Takes ownership of a file descriptor and watches it for readability.
class Input::EventFileDescriptor {
public:
// Takes ownership of |fd| and closes it on destruction. |watcher| is notified
// when |fd| becomes readable.
EventFileDescriptor(int fd, base::MessageLoopForIO::Watcher* watcher)
: fd_(fd),
fd_watcher_(new base::MessageLoopForIO::FileDescriptorWatcher) {
if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
fd, true, base::MessageLoopForIO::WATCH_READ, fd_watcher_.get(),
watcher)) {
LOG(ERROR) << "Unable to watch FD " << fd;
}
}
~EventFileDescriptor() {
fd_watcher_.reset();
if (!close(fd_))
PLOG(ERROR) << "Unable to close FD " << fd_;
}
int fd() const { return fd_; }
private:
// File descriptor that is being watched.
int fd_;
// Controller used to watch |fd_|.
scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> fd_watcher_;
DISALLOW_COPY_AND_ASSIGN(EventFileDescriptor);
};
Input::Input()
: lid_fd_(-1),
num_power_key_events_(0),
num_lid_events_(0),
wakeups_enabled_(true),
use_lid_(true),
power_button_to_skip_(kPowerButtonToSkip),
console_fd_(-1),
udev_(NULL) {
}
Input::~Input() {
if (udev_)
udev_->RemoveObserver(kInputUdevSubsystem, this);
if (console_fd_ >= 0)
close(console_fd_);
}
bool Input::Init(PrefsInterface* prefs, UdevInterface* udev) {
prefs->GetBool(kUseLidPref, &use_lid_);
std::string wakeup_inputs_str;
std::vector<std::string> wakeup_input_names;
if (prefs->GetString(kWakeupInputPref, &wakeup_inputs_str))
base::SplitString(wakeup_inputs_str, '\n', &wakeup_input_names);
for (vector<string>::const_iterator names_iter = wakeup_input_names.begin();
names_iter != wakeup_input_names.end(); ++names_iter) {
// Iterate through the vector of input names, and if not the empty string,
// put the input name into the wakeup_inputs_map_, mapping to -1.
// This indicates looking for input devices with this name, but there
// is no input number associated with the device just yet.
if ((*names_iter).length() > 0)
wakeup_inputs_map_[*names_iter] = -1;
}
bool legacy_power_button = false;
if (prefs->GetBool(kLegacyPowerButtonPref, &legacy_power_button) &&
legacy_power_button)
power_button_to_skip_ = kPowerButtonToSkipForLegacy;
udev_ = udev;
udev_->AddObserver(kInputUdevSubsystem, this);
// Don't bother doing anything more if we're running under a test.
if (!sysfs_input_path_for_testing_.empty())
return true;
if ((console_fd_ = open(kConsolePath, O_WRONLY)) == -1)
PLOG(ERROR) << "Unable to open " << kConsolePath;
RegisterInputWakeSources();
return RegisterInputDevices();
}
void Input::AddObserver(InputObserver* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void Input::RemoveObserver(InputObserver* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
#define BITS_PER_LONG (sizeof(long) * 8)
#define NUM_BITS(x) ((((x) - 1) / BITS_PER_LONG) + 1)
#define OFF(x) ((x) % BITS_PER_LONG)
#define BIT(x) (1UL << OFF(x))
#define LONG(x) ((x) / BITS_PER_LONG)
#define IS_BIT_SET(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
LidState Input::QueryLidState() {
if (lid_fd_ < 0)
return LID_NOT_PRESENT;
unsigned long switch_events[NUM_BITS(SW_LID + 1)];
memset(switch_events, 0, sizeof(switch_events));
if (ioctl(lid_fd_, EVIOCGBIT(EV_SW, SW_LID + 1), switch_events) < 0) {
PLOG(ERROR) << "Lid state ioctl() failed";
return LID_NOT_PRESENT;
}
if (IS_BIT_SET(SW_LID, switch_events)) {
ioctl(lid_fd_, EVIOCGSW(sizeof(switch_events)), switch_events);
return IS_BIT_SET(SW_LID, switch_events) ? LID_CLOSED : LID_OPEN;
} else {
return LID_NOT_PRESENT;
}
}
bool Input::IsUSBInputDeviceConnected() const {
base::FileEnumerator enumerator(
sysfs_input_path_for_testing_.empty() ?
base::FilePath(kSysClassInputPath) :
sysfs_input_path_for_testing_,
false,
static_cast<::base::FileEnumerator::FileType>(
base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS),
kInputMatchPattern);
for (base::FilePath path = enumerator.Next(); !path.empty();
path = enumerator.Next()) {
base::FilePath symlink_path;
if (!base::ReadSymbolicLink(path, &symlink_path))
continue;
const std::string& path_string = symlink_path.value();
// Skip bluetooth devices, which may be identified as USB devices.
if (path_string.find(kBluetoothMatchString) != std::string::npos)
continue;
// Now look for the USB devices that are not bluetooth.
size_t position = path_string.find(kUsbMatchString);
if (position == std::string::npos)
continue;
// Now that the string "usb" has been found, make sure it is a whole word
// and not just part of another word like "busbreaker".
bool usb_at_word_head =
position == 0 || !IsAsciiAlpha(path_string.at(position - 1));
bool usb_at_word_tail =
position + strlen(kUsbMatchString) == path_string.size() ||
!IsAsciiAlpha(path_string.at(position + strlen(kUsbMatchString)));
if (usb_at_word_head && usb_at_word_tail)
return true;
}
return false;
}
int Input::GetActiveVT() {
struct vt_stat state;
if (ioctl(console_fd_, VT_GETSTATE, &state) == -1) {
PLOG(ERROR) << "VT_GETSTATE ioctl on " << kConsolePath << "failed";
return -1;
}
return state.v_active;
}
bool Input::SetWakeInputsState(bool enable) {
wakeups_enabled_ = enable;
return SetInputWakeupStates();
}
void Input::SetTouchDevicesState(bool enable) {
if (access(kTouchControlPath, R_OK | X_OK) != 0)
return;
if (enable)
util::Launch(base::StringPrintf("%s --enable", kTouchControlPath));
else
util::Run(base::StringPrintf("%s --disable", kTouchControlPath));
}
void Input::OnFileCanReadWithoutBlocking(int fd) {
struct input_event events[64];
ssize_t read_size = HANDLE_EINTR(read(fd, events, sizeof(events)));
if (read_size < 0)
return;
const ssize_t num_events = read_size / sizeof(struct input_event);
if (!read_size || read_size % sizeof(struct input_event)) {
LOG(ERROR) << "Read " << read_size << " byte(s) while expecting "
<< sizeof(struct input_event) << "-byte events";
return;
}
for (ssize_t i = 0; i < num_events; i++) {
const input_event& event = events[i];
if (event.type == EV_SW && event.code == SW_LID) {
LidState state = event.value == 1 ? LID_CLOSED : LID_OPEN;
FOR_EACH_OBSERVER(InputObserver, observers_, OnLidEvent(state));
} else if (event.type == EV_KEY && event.code == KEY_POWER) {
ButtonState state = BUTTON_DOWN;
switch (event.value) {
case 0: state = BUTTON_UP; break;
case 1: state = BUTTON_DOWN; break;
case 2: state = BUTTON_REPEAT; break;
default: LOG(ERROR) << "Unhandled button state " << event.value;
}
FOR_EACH_OBSERVER(InputObserver, observers_, OnPowerButtonEvent(state));
}
}
}
void Input::OnFileCanWriteWithoutBlocking(int fd) {
NOTREACHED() << "Unexpected non-blocking write notification for FD " << fd;
}
void Input::OnUdevEvent(const std::string& subsystem,
const std::string& sysname,
UdevObserver::Action action) {
DCHECK_EQ(subsystem, kInputUdevSubsystem);
if (StartsWithASCII(sysname, kEventBaseName, true)) {
if (action == UdevObserver::ACTION_ADD)
AddEvent(sysname);
else if (action == UdevObserver::ACTION_REMOVE)
RemoveEvent(sysname);
} else if (StartsWithASCII(sysname, kInputBaseName, true)) {
if (action == UdevObserver::ACTION_ADD)
AddWakeInput(sysname);
else if (action == UdevObserver::ACTION_REMOVE)
RemoveWakeInput(sysname);
}
}
bool Input::RegisterInputDevices() {
DIR* dir = opendir(kDevInputPath);
int num_registered = 0;
if (!dir) {
PLOG(ERROR) << "opendir() failed for " << kDevInputPath;
return false;
}
struct dirent entry;
struct dirent* result;
while (readdir_r(dir, &entry, & result) == 0 && result) {
if (result->d_name[0]) {
if (AddEvent(result->d_name))
num_registered++;
}
}
if (closedir(dir) < 0)
PLOG(ERROR) << "closedir() failed for " << kDevInputPath;
LOG(INFO) << "Number of power button events registered: "
<< num_power_key_events_;
LOG(INFO) << "Number of lid events registered: " << num_lid_events_;
return true;
}
bool Input::RegisterInputWakeSources() {
DIR* dir = opendir(kSysClassInputPath);
if (!dir) {
PLOG(ERROR) << "opendir() failed for " << kSysClassInputPath;
return false;
}
struct dirent entry;
struct dirent* result;
while (readdir_r(dir, &entry, &result) == 0 && result) {
if (StartsWithASCII(result->d_name, kInputBaseName, true))
AddWakeInput(result->d_name);
}
if (closedir(dir) < 0)
PLOG(ERROR) << "closedir() failed for " << kSysClassInputPath;
return true;
}
bool Input::SetInputWakeupStates() {
bool result = true;
for (WakeupMap::iterator iter = wakeup_inputs_map_.begin();
iter != wakeup_inputs_map_.end(); iter++) {
int input_num = (*iter).second;
if (input_num != -1 && !SetWakeupState(input_num, wakeups_enabled_)) {
result = false;
LOG(WARNING) << "Failed to set power/wakeup for input" << input_num;
}
}
return result;
}
bool Input::SetWakeupState(int input_num, bool enabled) {
std::string name = base::StringPrintf("%s%d", kInputBaseName, input_num);
base::FilePath path = base::FilePath(kSysClassInputPath).
Append(name).Append("device/power/wakeup");
const char* state = enabled ? kWakeupEnabled : kWakeupDisabled;
if (!base::WriteFile(path, state, strlen(state))) {
LOG(ERROR) << "Failed to write to " << path.value();
return false;
}
LOG(INFO) << "Set " << path.value() << " to " << state;
return true;
}
bool Input::AddEvent(const std::string& name) {
// Avoid logging warnings for files that should be ignored.
const char* kEventsToSkip[] = {
".",
"..",
"by-id",
"by-path",
};
for (size_t i = 0; i < arraysize(kEventsToSkip); ++i) {
if (name == kEventsToSkip[i])
return false;
}
int event_num = -1;
if (!GetSuffixNumber(name, kEventBaseName, &event_num)) {
LOG(WARNING) << name << " is not a valid event name; not adding as event";
return false;
}
InputMap::iterator iter = registered_inputs_.find(event_num);
if (iter != registered_inputs_.end()) {
LOG(WARNING) << "Input event " << event_num << " already registered";
return false;
}
base::FilePath event_path = base::FilePath(kDevInputPath).Append(name);
int event_fd;
if (access(event_path.value().c_str(), R_OK) != 0) {
LOG(WARNING) << "Missing read access to " << event_path.value();
return false;
}
if ((event_fd = open(event_path.value().c_str(), O_RDONLY)) < 0) {
PLOG(ERROR) << "open() failed for " << event_path.value();
return false;
}
if (!RegisterInputEvent(event_fd, event_num)) {
if (close(event_fd) < 0) // event not registered, closing.
PLOG(ERROR) << "close() failed for " << event_path.value();
return false;
}
return true;
}
bool Input::RemoveEvent(const std::string& name) {
int event_num = -1;
if (!GetSuffixNumber(name, kEventBaseName, &event_num)) {
LOG(WARNING) << name << " is not a valid event name; not removing event";
return false;
}
InputMap::iterator iter = registered_inputs_.find(event_num);
if (iter == registered_inputs_.end()) {
LOG(WARNING) << "Input event " << name << " not registered; "
<< "nothing to remove";
return false;
}
registered_inputs_.erase(iter);
return true;
}
bool Input::AddWakeInput(const std::string& name) {
int input_num = -1;
if (wakeup_inputs_map_.empty() ||
!GetSuffixNumber(name, kInputBaseName, &input_num))
return false;
base::FilePath device_name_path =
base::FilePath(kSysClassInputPath).Append(name).Append("name");
if (access(device_name_path.value().c_str(), R_OK)) {
LOG(WARNING) << "Missing read access to " << device_name_path.value();
return false;
}
std::string input_name;
if (!base::ReadFileToString(device_name_path, &input_name)) {
LOG(WARNING) << "Failed to read input name from "
<< device_name_path.value();
return false;
}
base::TrimWhitespaceASCII(input_name, base::TRIM_TRAILING, &input_name);
WakeupMap::iterator iter = wakeup_inputs_map_.find(input_name);
if (iter == wakeup_inputs_map_.end()) {
// Not on the list of wakeup input devices
return false;
}
if (!SetWakeupState(input_num, wakeups_enabled_)) {
LOG(ERROR) << "Error adding wakeup source; cannot write to power/wakeup";
return false;
}
wakeup_inputs_map_[input_name] = input_num;
LOG(INFO) << "Added wakeup source " << name << " (" << input_name << ")";
return true;
}
bool Input::RemoveWakeInput(const std::string& name) {
int input_num = -1;
if (wakeup_inputs_map_.empty() ||
!GetSuffixNumber(name, kInputBaseName, &input_num))
return false;
WakeupMap::iterator iter;
for (iter = wakeup_inputs_map_.begin();
iter != wakeup_inputs_map_.end(); iter++) {
if ((*iter).second == input_num) {
wakeup_inputs_map_[(*iter).first] = -1;
LOG(INFO) << "Removed wakeup source " << name
<< " (" << (*iter).first << ")";
}
}
return false;
}
bool Input::RegisterInputEvent(int fd, int event_num) {
char name[256] = "Unknown";
if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
PLOG(ERROR) << "Could not get name of device (FD " << fd << ", event "
<< event_num << ")";
return false;
} else {
VLOG(1) << "Device name: " << name;
}
char phys[256] = "Unknown";
if (ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0) {
PLOG(ERROR) << "Could not get topo phys path of device " << name;
return false;
} else {
VLOG(1) << "Device topo phys: " << phys;
}
if (StartsWithASCII(phys, power_button_to_skip_, true /* case_sensitive */)) {
VLOG(1) << "Skipping interface: " << phys;
return false;
}
unsigned long events[NUM_BITS(EV_MAX)];
memset(events, 0, sizeof(events));
if (ioctl(fd, EVIOCGBIT(0, EV_MAX), events) < 0) {
PLOG(ERROR) << "EV_MAX ioctl failed for device " << name;
return false;
}
bool should_watch = false;
// Power button.
if (IS_BIT_SET(EV_KEY, events)) {
unsigned long keys[NUM_BITS(KEY_MAX)];
memset(keys, 0, sizeof(keys));
if (ioctl(fd, EVIOCGBIT(EV_KEY, KEY_MAX), keys) < 0) {
PLOG(ERROR) << "KEY_MAX ioctl failed for device " << name;
} else if (IS_BIT_SET(KEY_POWER, keys)) {
LOG(INFO) << "Watching " << phys << " (" << name << ") for power button";
should_watch = true;
num_power_key_events_++;
}
}
// Lid switch. Note that it's possible for a power button and lid switch to
// share a single event file.
if (IS_BIT_SET(EV_SW, events)) {
unsigned long switch_events[NUM_BITS(SW_LID + 1)];
memset(switch_events, 0, sizeof(switch_events));
if (ioctl(fd, EVIOCGBIT(EV_SW, SW_LID + 1), switch_events) < 0) {
PLOG(ERROR) << "SW_LID ioctl failed for device " << name;
} else if (use_lid_ && IS_BIT_SET(SW_LID, switch_events)) {
LOG(INFO) << "Watching " << phys << " (" << name << ") for lid switch";
should_watch = true;
num_lid_events_++;
if (lid_fd_ >= 0)
LOG(WARNING) << "Multiple lid events found on system";
lid_fd_ = fd;
}
}
if (!should_watch)
return false;
registered_inputs_.insert(std::make_pair(event_num,
make_linked_ptr(new EventFileDescriptor(fd, this))));
return true;
}
} // namespace system
} // namespace power_manager