blob: 4df2cb570ca10942b318e577cab25fd9185d5d41 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "device/hid/input_service_linux.h"
#include <memory>
#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/scoped_observer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "device/core/device_monitor_linux.h"
#include "device/udev_linux/udev.h"
namespace device {
namespace {
const char kSubsystemHid[] = "hid";
const char kSubsystemInput[] = "input";
const char kSubsystemMisc[] = "misc";
const char kTypeBluetooth[] = "bluetooth";
const char kTypeUsb[] = "usb";
const char kTypeSerio[] = "serio";
const char kIdInputAccelerometer[] = "ID_INPUT_ACCELEROMETER";
const char kIdInputJoystick[] = "ID_INPUT_JOYSTICK";
const char kIdInputKey[] = "ID_INPUT_KEY";
const char kIdInputKeyboard[] = "ID_INPUT_KEYBOARD";
const char kIdInputMouse[] = "ID_INPUT_MOUSE";
const char kIdInputTablet[] = "ID_INPUT_TABLET";
const char kIdInputTouchpad[] = "ID_INPUT_TOUCHPAD";
const char kIdInputTouchscreen[] = "ID_INPUT_TOUCHSCREEN";
// The instance will be reset when message loop destroys.
base::LazyInstance<std::unique_ptr<InputServiceLinux>>::Leaky
g_input_service_linux_ptr = LAZY_INSTANCE_INITIALIZER;
bool GetBoolProperty(udev_device* device, const char* key) {
CHECK(device);
CHECK(key);
const char* property = udev_device_get_property_value(device, key);
if (!property)
return false;
int value;
if (!base::StringToInt(property, &value)) {
LOG(ERROR) << "Not an integer value for " << key << " property";
return false;
}
return (value != 0);
}
InputServiceLinux::InputDeviceInfo::Type GetDeviceType(udev_device* device) {
// Bluetooth classic hid devices are registered under bluetooth subsystem.
// Bluetooth LE hid devices are registered under virtual misc/hid subsystems.
if (udev_device_get_parent_with_subsystem_devtype(device, kTypeBluetooth,
NULL) ||
(udev_device_get_parent_with_subsystem_devtype(device, kSubsystemHid,
NULL) &&
udev_device_get_parent_with_subsystem_devtype(device, kSubsystemMisc,
NULL))) {
return InputServiceLinux::InputDeviceInfo::TYPE_BLUETOOTH;
}
if (udev_device_get_parent_with_subsystem_devtype(device, kTypeUsb, NULL))
return InputServiceLinux::InputDeviceInfo::TYPE_USB;
if (udev_device_get_parent_with_subsystem_devtype(device, kTypeSerio, NULL))
return InputServiceLinux::InputDeviceInfo::TYPE_SERIO;
return InputServiceLinux::InputDeviceInfo::TYPE_UNKNOWN;
}
std::string GetParentDeviceName(udev_device* device, const char* subsystem) {
udev_device* parent =
udev_device_get_parent_with_subsystem_devtype(device, subsystem, NULL);
if (!parent)
return std::string();
const char* name = udev_device_get_property_value(parent, "NAME");
if (!name)
return std::string();
std::string result;
base::TrimString(name, "\"", &result);
return result;
}
class InputServiceLinuxImpl : public InputServiceLinux,
public DeviceMonitorLinux::Observer {
public:
// Implements DeviceMonitorLinux::Observer:
void OnDeviceAdded(udev_device* device) override;
void OnDeviceRemoved(udev_device* device) override;
void WillDestroyMonitorMessageLoop() override;
private:
friend class InputServiceLinux;
InputServiceLinuxImpl();
~InputServiceLinuxImpl() override;
ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_;
DISALLOW_COPY_AND_ASSIGN(InputServiceLinuxImpl);
};
InputServiceLinuxImpl::InputServiceLinuxImpl() : observer_(this) {
base::ThreadRestrictions::AssertIOAllowed();
DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance();
observer_.Add(monitor);
monitor->Enumerate(base::Bind(&InputServiceLinuxImpl::OnDeviceAdded,
base::Unretained(this)));
}
InputServiceLinuxImpl::~InputServiceLinuxImpl() {
}
void InputServiceLinuxImpl::OnDeviceAdded(udev_device* device) {
DCHECK(CalledOnValidThread());
if (!device)
return;
const char* devnode = udev_device_get_devnode(device);
if (!devnode)
return;
InputDeviceInfo info;
info.id = devnode;
const char* subsystem = udev_device_get_subsystem(device);
if (!subsystem)
return;
if (strcmp(subsystem, kSubsystemHid) == 0) {
info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_HID;
info.name = GetParentDeviceName(device, kSubsystemHid);
} else if (strcmp(subsystem, kSubsystemInput) == 0) {
info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_INPUT;
info.name = GetParentDeviceName(device, kSubsystemInput);
} else {
return;
}
info.type = GetDeviceType(device);
info.is_accelerometer = GetBoolProperty(device, kIdInputAccelerometer);
info.is_joystick = GetBoolProperty(device, kIdInputJoystick);
info.is_key = GetBoolProperty(device, kIdInputKey);
info.is_keyboard = GetBoolProperty(device, kIdInputKeyboard);
info.is_mouse = GetBoolProperty(device, kIdInputMouse);
info.is_tablet = GetBoolProperty(device, kIdInputTablet);
info.is_touchpad = GetBoolProperty(device, kIdInputTouchpad);
info.is_touchscreen = GetBoolProperty(device, kIdInputTouchscreen);
AddDevice(info);
}
void InputServiceLinuxImpl::OnDeviceRemoved(udev_device* device) {
DCHECK(CalledOnValidThread());
if (!device)
return;
const char* devnode = udev_device_get_devnode(device);
if (devnode)
RemoveDevice(devnode);
}
void InputServiceLinuxImpl::WillDestroyMonitorMessageLoop() {
DCHECK(CalledOnValidThread());
g_input_service_linux_ptr.Get().reset(nullptr);
}
} // namespace
InputServiceLinux::InputDeviceInfo::InputDeviceInfo()
: subsystem(SUBSYSTEM_UNKNOWN),
type(TYPE_UNKNOWN),
is_accelerometer(false),
is_joystick(false),
is_key(false),
is_keyboard(false),
is_mouse(false),
is_tablet(false),
is_touchpad(false),
is_touchscreen(false) {}
InputServiceLinux::InputDeviceInfo::InputDeviceInfo(
const InputDeviceInfo& other) = default;
InputServiceLinux::InputServiceLinux() {
}
InputServiceLinux::~InputServiceLinux() {
DCHECK(CalledOnValidThread());
}
// static
InputServiceLinux* InputServiceLinux::GetInstance() {
if (!HasInstance())
g_input_service_linux_ptr.Get().reset(new InputServiceLinuxImpl());
return g_input_service_linux_ptr.Get().get();
}
// static
bool InputServiceLinux::HasInstance() {
return g_input_service_linux_ptr.Get().get();
}
// static
void InputServiceLinux::SetForTesting(InputServiceLinux* service) {
g_input_service_linux_ptr.Get().reset(service);
}
void InputServiceLinux::AddObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
if (observer)
observers_.AddObserver(observer);
}
void InputServiceLinux::RemoveObserver(Observer* observer) {
DCHECK(CalledOnValidThread());
if (observer)
observers_.RemoveObserver(observer);
}
void InputServiceLinux::GetDevices(std::vector<InputDeviceInfo>* devices) {
DCHECK(CalledOnValidThread());
for (DeviceMap::iterator it = devices_.begin(), ie = devices_.end(); it != ie;
++it) {
devices->push_back(it->second);
}
}
bool InputServiceLinux::GetDeviceInfo(const std::string& id,
InputDeviceInfo* info) const {
DCHECK(CalledOnValidThread());
DeviceMap::const_iterator it = devices_.find(id);
if (it == devices_.end())
return false;
*info = it->second;
return true;
}
void InputServiceLinux::AddDevice(const InputDeviceInfo& info) {
devices_[info.id] = info;
FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceAdded(info));
}
void InputServiceLinux::RemoveDevice(const std::string& id) {
devices_.erase(id);
FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceRemoved(id));
}
bool InputServiceLinux::CalledOnValidThread() const {
return thread_checker_.CalledOnValidThread();
}
} // namespace device