1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <libudev.h>
6
7#include "base/bind.h"
8#include "base/lazy_instance.h"
9#include "base/logging.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_util.h"
12#include "base/threading/thread_restrictions.h"
13#include "device/hid/input_service_linux.h"
14
15namespace device {
16
17namespace {
18
19const char kSubsystemHid[] = "hid";
20const char kSubsystemInput[] = "input";
21const char kTypeBluetooth[] = "bluetooth";
22const char kTypeUsb[] = "usb";
23const char kTypeSerio[] = "serio";
24const char kIdInputAccelerometer[] = "ID_INPUT_ACCELEROMETER";
25const char kIdInputJoystick[] = "ID_INPUT_JOYSTICK";
26const char kIdInputKey[] = "ID_INPUT_KEY";
27const char kIdInputKeyboard[] = "ID_INPUT_KEYBOARD";
28const char kIdInputMouse[] = "ID_INPUT_MOUSE";
29const char kIdInputTablet[] = "ID_INPUT_TABLET";
30const char kIdInputTouchpad[] = "ID_INPUT_TOUCHPAD";
31const char kIdInputTouchscreen[] = "ID_INPUT_TOUCHSCREEN";
32
33// The instance will be reset when message loop destroys.
34base::LazyInstance<scoped_ptr<InputServiceLinux> >::Leaky
35    g_input_service_linux_ptr = LAZY_INSTANCE_INITIALIZER;
36
37bool GetBoolProperty(udev_device* device, const char* key) {
38  CHECK(device);
39  CHECK(key);
40  const char* property = udev_device_get_property_value(device, key);
41  if (!property)
42    return false;
43  int value;
44  if (!base::StringToInt(property, &value)) {
45    LOG(ERROR) << "Not an integer value for " << key << " property";
46    return false;
47  }
48  return (value != 0);
49}
50
51InputServiceLinux::InputDeviceInfo::Type GetDeviceType(udev_device* device) {
52  if (udev_device_get_parent_with_subsystem_devtype(
53          device, kTypeBluetooth, NULL)) {
54    return InputServiceLinux::InputDeviceInfo::TYPE_BLUETOOTH;
55  }
56  if (udev_device_get_parent_with_subsystem_devtype(device, kTypeUsb, NULL))
57    return InputServiceLinux::InputDeviceInfo::TYPE_USB;
58  if (udev_device_get_parent_with_subsystem_devtype(device, kTypeSerio, NULL))
59    return InputServiceLinux::InputDeviceInfo::TYPE_SERIO;
60  return InputServiceLinux::InputDeviceInfo::TYPE_UNKNOWN;
61}
62
63std::string GetParentDeviceName(udev_device* device, const char* subsystem) {
64  udev_device* parent =
65      udev_device_get_parent_with_subsystem_devtype(device, subsystem, NULL);
66  if (!parent)
67    return std::string();
68  const char* name = udev_device_get_property_value(parent, "NAME");
69  if (!name)
70    return std::string();
71  std::string result;
72  base::TrimString(name, "\"", &result);
73  return result;
74}
75
76class InputServiceLinuxImpl : public InputServiceLinux,
77                              public DeviceMonitorLinux::Observer {
78 public:
79  // Implements DeviceMonitorLinux::Observer:
80  virtual void OnDeviceAdded(udev_device* device) OVERRIDE;
81  virtual void OnDeviceRemoved(udev_device* device) OVERRIDE;
82
83 private:
84  friend class InputServiceLinux;
85
86  InputServiceLinuxImpl();
87  virtual ~InputServiceLinuxImpl();
88
89  DISALLOW_COPY_AND_ASSIGN(InputServiceLinuxImpl);
90};
91
92InputServiceLinuxImpl::InputServiceLinuxImpl() {
93  DeviceMonitorLinux::GetInstance()->AddObserver(this);
94  DeviceMonitorLinux::GetInstance()->Enumerate(base::Bind(
95      &InputServiceLinuxImpl::OnDeviceAdded, base::Unretained(this)));
96}
97
98InputServiceLinuxImpl::~InputServiceLinuxImpl() {
99  if (DeviceMonitorLinux::HasInstance())
100    DeviceMonitorLinux::GetInstance()->RemoveObserver(this);
101}
102
103void InputServiceLinuxImpl::OnDeviceAdded(udev_device* device) {
104  DCHECK(CalledOnValidThread());
105  if (!device)
106    return;
107  const char* devnode = udev_device_get_devnode(device);
108  if (!devnode)
109    return;
110
111  InputDeviceInfo info;
112  info.id = devnode;
113
114  const char* subsystem = udev_device_get_subsystem(device);
115  if (!subsystem)
116    return;
117  if (strcmp(subsystem, kSubsystemHid) == 0) {
118    info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_HID;
119    info.name = GetParentDeviceName(device, kSubsystemHid);
120  } else if (strcmp(subsystem, kSubsystemInput) == 0) {
121    info.subsystem = InputServiceLinux::InputDeviceInfo::SUBSYSTEM_INPUT;
122    info.name = GetParentDeviceName(device, kSubsystemInput);
123  } else {
124    return;
125  }
126
127  info.type = GetDeviceType(device);
128
129  info.is_accelerometer = GetBoolProperty(device, kIdInputAccelerometer);
130  info.is_joystick = GetBoolProperty(device, kIdInputJoystick);
131  info.is_key = GetBoolProperty(device, kIdInputKey);
132  info.is_keyboard = GetBoolProperty(device, kIdInputKeyboard);
133  info.is_mouse = GetBoolProperty(device, kIdInputMouse);
134  info.is_tablet = GetBoolProperty(device, kIdInputTablet);
135  info.is_touchpad = GetBoolProperty(device, kIdInputTouchpad);
136  info.is_touchscreen = GetBoolProperty(device, kIdInputTouchscreen);
137
138  AddDevice(info);
139}
140
141void InputServiceLinuxImpl::OnDeviceRemoved(udev_device* device) {
142  DCHECK(CalledOnValidThread());
143  if (!device)
144    return;
145  const char* devnode = udev_device_get_devnode(device);
146  if (devnode)
147    RemoveDevice(devnode);
148}
149
150}  // namespace
151
152InputServiceLinux::InputDeviceInfo::InputDeviceInfo()
153    : subsystem(SUBSYSTEM_UNKNOWN),
154      type(TYPE_UNKNOWN),
155      is_accelerometer(false),
156      is_joystick(false),
157      is_key(false),
158      is_keyboard(false),
159      is_mouse(false),
160      is_tablet(false),
161      is_touchpad(false),
162      is_touchscreen(false) {}
163
164InputServiceLinux::InputServiceLinux() {
165  base::ThreadRestrictions::AssertIOAllowed();
166  base::MessageLoop::current()->AddDestructionObserver(this);
167}
168
169InputServiceLinux::~InputServiceLinux() {
170  DCHECK(CalledOnValidThread());
171  base::MessageLoop::current()->RemoveDestructionObserver(this);
172}
173
174// static
175InputServiceLinux* InputServiceLinux::GetInstance() {
176  if (!HasInstance())
177    g_input_service_linux_ptr.Get().reset(new InputServiceLinuxImpl());
178  return g_input_service_linux_ptr.Get().get();
179}
180
181// static
182bool InputServiceLinux::HasInstance() {
183  return g_input_service_linux_ptr.Get().get();
184}
185
186// static
187void InputServiceLinux::SetForTesting(InputServiceLinux* service) {
188  g_input_service_linux_ptr.Get().reset(service);
189}
190
191void InputServiceLinux::AddObserver(Observer* observer) {
192  DCHECK(CalledOnValidThread());
193  if (observer)
194    observers_.AddObserver(observer);
195}
196
197void InputServiceLinux::RemoveObserver(Observer* observer) {
198  DCHECK(CalledOnValidThread());
199  if (observer)
200    observers_.RemoveObserver(observer);
201}
202
203void InputServiceLinux::GetDevices(std::vector<InputDeviceInfo>* devices) {
204  DCHECK(CalledOnValidThread());
205  for (DeviceMap::iterator it = devices_.begin(), ie = devices_.end(); it != ie;
206       ++it) {
207    devices->push_back(it->second);
208  }
209}
210
211bool InputServiceLinux::GetDeviceInfo(const std::string& id,
212                                      InputDeviceInfo* info) const {
213  DCHECK(CalledOnValidThread());
214  DeviceMap::const_iterator it = devices_.find(id);
215  if (it == devices_.end())
216    return false;
217  *info = it->second;
218  return true;
219}
220
221void InputServiceLinux::WillDestroyCurrentMessageLoop() {
222  DCHECK(CalledOnValidThread());
223  g_input_service_linux_ptr.Get().reset(NULL);
224}
225
226void InputServiceLinux::AddDevice(const InputDeviceInfo& info) {
227  devices_[info.id] = info;
228  FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceAdded(info));
229}
230
231void InputServiceLinux::RemoveDevice(const std::string& id) {
232  devices_.erase(id);
233  FOR_EACH_OBSERVER(Observer, observers_, OnInputDeviceRemoved(id));
234}
235
236bool InputServiceLinux::CalledOnValidThread() const {
237  return thread_checker_.CalledOnValidThread();
238}
239
240}  // namespace device
241