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.
5 #include "device/hid/hid_service_linux.h"
10 #include "base/bind.h"
11 #include "base/files/file.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/location.h"
15 #include "base/logging.h"
16 #include "base/scoped_observer.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "base/threading/thread_restrictions.h"
21 #include "device/hid/device_monitor_linux.h"
22 #include "device/hid/hid_connection_linux.h"
23 #include "device/hid/hid_device_info.h"
24 #include "device/hid/hid_report_descriptor.h"
25 #include "device/udev_linux/scoped_udev.h"
27 #if defined(OS_CHROMEOS)
28 #include "base/sys_info.h"
29 #include "chromeos/dbus/dbus_thread_manager.h"
30 #include "chromeos/dbus/permission_broker_client.h"
31 #endif // defined(OS_CHROMEOS)
37 const char kHidrawSubsystem
[] = "hidraw";
38 const char kHIDID
[] = "HID_ID";
39 const char kHIDName
[] = "HID_NAME";
40 const char kHIDUnique
[] = "HID_UNIQ";
41 const char kSysfsReportDescriptorKey
[] = "report_descriptor";
45 struct HidServiceLinux::ConnectParams
{
46 ConnectParams(const HidDeviceInfo
& device_info
,
47 const ConnectCallback
& callback
,
48 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
,
49 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
50 : device_info(device_info
),
52 task_runner(task_runner
),
53 file_task_runner(file_task_runner
) {}
56 HidDeviceInfo device_info
;
57 ConnectCallback callback
;
58 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
;
59 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
;
60 base::File device_file
;
63 class HidServiceLinux::Helper
: public DeviceMonitorLinux::Observer
,
64 public base::MessageLoop::DestructionObserver
{
66 Helper(base::WeakPtr
<HidServiceLinux
> service
,
67 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
68 : observer_(this), service_(service
), task_runner_(task_runner
) {
69 DeviceMonitorLinux
* monitor
= DeviceMonitorLinux::GetInstance();
70 observer_
.Add(monitor
);
72 base::Bind(&Helper::OnDeviceAdded
, base::Unretained(this)));
73 task_runner
->PostTask(
75 base::Bind(&HidServiceLinux::FirstEnumerationComplete
, service_
));
78 ~Helper() override
{ DCHECK(thread_checker_
.CalledOnValidThread()); }
81 // DeviceMonitorLinux::Observer:
82 void OnDeviceAdded(udev_device
* device
) override
{
83 DCHECK(thread_checker_
.CalledOnValidThread());
84 const char* device_path
= udev_device_get_syspath(device
);
88 const char* subsystem
= udev_device_get_subsystem(device
);
89 if (!subsystem
|| strcmp(subsystem
, kHidrawSubsystem
) != 0) {
93 HidDeviceInfo device_info
;
94 device_info
.device_id
= device_path
;
96 const char* str_property
= udev_device_get_devnode(device
);
100 device_info
.device_node
= str_property
;
102 udev_device
* parent
= udev_device_get_parent(device
);
107 const char* hid_id
= udev_device_get_property_value(parent
, kHIDID
);
112 std::vector
<std::string
> parts
;
113 base::SplitString(hid_id
, ':', &parts
);
114 if (parts
.size() != 3) {
118 uint32_t int_property
= 0;
119 if (HexStringToUInt(base::StringPiece(parts
[1]), &int_property
)) {
120 device_info
.vendor_id
= int_property
;
123 if (HexStringToUInt(base::StringPiece(parts
[2]), &int_property
)) {
124 device_info
.product_id
= int_property
;
127 str_property
= udev_device_get_property_value(parent
, kHIDUnique
);
128 if (str_property
!= NULL
) {
129 device_info
.serial_number
= str_property
;
132 str_property
= udev_device_get_property_value(parent
, kHIDName
);
133 if (str_property
!= NULL
) {
134 device_info
.product_name
= str_property
;
137 const char* parent_sysfs_path
= udev_device_get_syspath(parent
);
138 if (!parent_sysfs_path
) {
141 base::FilePath report_descriptor_path
=
142 base::FilePath(parent_sysfs_path
).Append(kSysfsReportDescriptorKey
);
143 std::string report_descriptor_str
;
144 if (!base::ReadFileToString(report_descriptor_path
,
145 &report_descriptor_str
)) {
149 HidReportDescriptor
report_descriptor(
150 reinterpret_cast<uint8_t*>(&report_descriptor_str
[0]),
151 report_descriptor_str
.length());
152 report_descriptor
.GetDetails(
153 &device_info
.collections
, &device_info
.has_report_id
,
154 &device_info
.max_input_report_size
, &device_info
.max_output_report_size
,
155 &device_info
.max_feature_report_size
);
157 task_runner_
->PostTask(FROM_HERE
, base::Bind(&HidServiceLinux::AddDevice
,
158 service_
, device_info
));
161 void OnDeviceRemoved(udev_device
* device
) override
{
162 DCHECK(thread_checker_
.CalledOnValidThread());
163 const char* device_path
= udev_device_get_syspath(device
);
165 task_runner_
->PostTask(
166 FROM_HERE
, base::Bind(&HidServiceLinux::RemoveDevice
, service_
,
167 std::string(device_path
)));
171 // base::MessageLoop::DestructionObserver:
172 void WillDestroyCurrentMessageLoop() override
{
173 DCHECK(thread_checker_
.CalledOnValidThread());
174 base::MessageLoop::current()->RemoveDestructionObserver(this);
178 base::ThreadChecker thread_checker_
;
179 ScopedObserver
<DeviceMonitorLinux
, DeviceMonitorLinux::Observer
> observer_
;
181 // This weak pointer is only valid when checked on this task runner.
182 base::WeakPtr
<HidServiceLinux
> service_
;
183 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
186 HidServiceLinux::HidServiceLinux(
187 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
188 : file_task_runner_(file_task_runner
), weak_factory_(this) {
189 task_runner_
= base::ThreadTaskRunnerHandle::Get();
190 // The device watcher is passed a weak pointer back to this service so that it
191 // can be cleaned up after the service is destroyed however this weak pointer
192 // must be constructed on the this thread where it will be checked.
193 file_task_runner_
->PostTask(
194 FROM_HERE
, base::Bind(&HidServiceLinux::StartHelper
,
195 weak_factory_
.GetWeakPtr(), task_runner_
));
199 void HidServiceLinux::StartHelper(
200 base::WeakPtr
<HidServiceLinux
> weak_ptr
,
201 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
) {
202 // Helper is a message loop destruction observer and will delete itself when
203 // this thread's message loop is destroyed.
204 new Helper(weak_ptr
, task_runner
);
207 void HidServiceLinux::Connect(const HidDeviceId
& device_id
,
208 const ConnectCallback
& callback
) {
209 DCHECK(thread_checker_
.CalledOnValidThread());
211 const auto& map_entry
= devices().find(device_id
);
212 if (map_entry
== devices().end()) {
213 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
216 const HidDeviceInfo
& device_info
= map_entry
->second
;
218 scoped_ptr
<ConnectParams
> params(new ConnectParams(
219 device_info
, callback
, task_runner_
, file_task_runner_
));
221 #if defined(OS_CHROMEOS)
222 if (base::SysInfo::IsRunningOnChromeOS()) {
223 chromeos::PermissionBrokerClient
* client
=
224 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient();
225 DCHECK(client
) << "Could not get permission broker client.";
227 client
->RequestPathAccess(
228 device_info
.device_node
, -1,
229 base::Bind(&HidServiceLinux::OnRequestPathAccessComplete
,
230 base::Passed(¶ms
)));
232 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
236 #endif // defined(OS_CHROMEOS)
238 file_task_runner_
->PostTask(
240 base::Bind(&HidServiceLinux::OpenDevice
, base::Passed(¶ms
)));
243 HidServiceLinux::~HidServiceLinux() {
244 file_task_runner_
->DeleteSoon(FROM_HERE
, helper_
.release());
247 #if defined(OS_CHROMEOS)
249 void HidServiceLinux::OnRequestPathAccessComplete(
250 scoped_ptr
<ConnectParams
> params
,
253 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
=
254 params
->file_task_runner
;
255 file_task_runner
->PostTask(
257 base::Bind(&HidServiceLinux::OpenDevice
, base::Passed(¶ms
)));
259 params
->callback
.Run(nullptr);
262 #endif // defined(OS_CHROMEOS)
265 void HidServiceLinux::OpenDevice(scoped_ptr
<ConnectParams
> params
) {
266 base::ThreadRestrictions::AssertIOAllowed();
267 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
= params
->task_runner
;
268 base::FilePath
device_path(params
->device_info
.device_node
);
269 base::File
& device_file
= params
->device_file
;
271 base::File::FLAG_OPEN
| base::File::FLAG_READ
| base::File::FLAG_WRITE
;
272 device_file
.Initialize(device_path
, flags
);
273 if (!device_file
.IsValid()) {
274 base::File::Error file_error
= device_file
.error_details();
276 if (file_error
== base::File::FILE_ERROR_ACCESS_DENIED
) {
277 VLOG(1) << "Access denied opening device read-write, trying read-only.";
278 flags
= base::File::FLAG_OPEN
| base::File::FLAG_READ
;
279 device_file
.Initialize(device_path
, flags
);
282 if (!device_file
.IsValid()) {
283 LOG(ERROR
) << "Failed to open '" << params
->device_info
.device_node
<< "': "
284 << base::File::ErrorToString(device_file
.error_details());
285 task_runner
->PostTask(FROM_HERE
, base::Bind(params
->callback
, nullptr));
289 int result
= fcntl(device_file
.GetPlatformFile(), F_GETFL
);
291 PLOG(ERROR
) << "Failed to get flags from the device file descriptor";
292 task_runner
->PostTask(FROM_HERE
, base::Bind(params
->callback
, nullptr));
296 result
= fcntl(device_file
.GetPlatformFile(), F_SETFL
, result
| O_NONBLOCK
);
298 PLOG(ERROR
) << "Failed to set the non-blocking flag on the device fd";
299 task_runner
->PostTask(FROM_HERE
, base::Bind(params
->callback
, nullptr));
303 task_runner
->PostTask(FROM_HERE
, base::Bind(&HidServiceLinux::ConnectImpl
,
304 base::Passed(¶ms
)));
308 void HidServiceLinux::ConnectImpl(scoped_ptr
<ConnectParams
> params
) {
309 DCHECK(params
->device_file
.IsValid());
310 params
->callback
.Run(make_scoped_refptr(
311 new HidConnectionLinux(params
->device_info
, params
->device_file
.Pass(),
312 params
->file_task_runner
)));
315 } // namespace device