OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "device/hid/hid_service_linux.h" | 5 #include "device/hid/hid_service_linux.h" |
6 | 6 |
| 7 #include <fcntl.h> |
7 #include <string> | 8 #include <string> |
8 | 9 |
9 #include "base/bind.h" | 10 #include "base/bind.h" |
10 #include "base/files/file.h" | 11 #include "base/files/file.h" |
11 #include "base/files/file_path.h" | 12 #include "base/files/file_path.h" |
12 #include "base/files/file_util.h" | 13 #include "base/files/file_util.h" |
| 14 #include "base/location.h" |
13 #include "base/logging.h" | 15 #include "base/logging.h" |
14 #include "base/stl_util.h" | 16 #include "base/scoped_observer.h" |
15 #include "base/strings/string_number_conversions.h" | 17 #include "base/strings/string_number_conversions.h" |
16 #include "base/strings/string_piece.h" | |
17 #include "base/strings/string_split.h" | 18 #include "base/strings/string_split.h" |
18 #include "base/thread_task_runner_handle.h" | 19 #include "base/thread_task_runner_handle.h" |
19 #include "base/threading/thread_restrictions.h" | 20 #include "base/threading/thread_restrictions.h" |
| 21 #include "device/hid/device_monitor_linux.h" |
20 #include "device/hid/hid_connection_linux.h" | 22 #include "device/hid/hid_connection_linux.h" |
21 #include "device/hid/hid_device_info.h" | 23 #include "device/hid/hid_device_info.h" |
22 #include "device/hid/hid_report_descriptor.h" | 24 #include "device/hid/hid_report_descriptor.h" |
23 #include "device/udev_linux/scoped_udev.h" | 25 #include "device/udev_linux/scoped_udev.h" |
24 | 26 |
25 #if defined(OS_CHROMEOS) | 27 #if defined(OS_CHROMEOS) |
26 #include "base/sys_info.h" | 28 #include "base/sys_info.h" |
27 #include "chromeos/dbus/dbus_thread_manager.h" | 29 #include "chromeos/dbus/dbus_thread_manager.h" |
28 #include "chromeos/dbus/permission_broker_client.h" | 30 #include "chromeos/dbus/permission_broker_client.h" |
29 #endif // defined(OS_CHROMEOS) | 31 #endif // defined(OS_CHROMEOS) |
30 | 32 |
31 namespace device { | 33 namespace device { |
32 | 34 |
33 namespace { | 35 namespace { |
34 | 36 |
35 const char kHidrawSubsystem[] = "hidraw"; | 37 const char kHidrawSubsystem[] = "hidraw"; |
36 const char kHIDID[] = "HID_ID"; | 38 const char kHIDID[] = "HID_ID"; |
37 const char kHIDName[] = "HID_NAME"; | 39 const char kHIDName[] = "HID_NAME"; |
38 const char kHIDUnique[] = "HID_UNIQ"; | 40 const char kHIDUnique[] = "HID_UNIQ"; |
39 const char kSysfsReportDescriptorKey[] = "report_descriptor"; | 41 const char kSysfsReportDescriptorKey[] = "report_descriptor"; |
40 | 42 |
| 43 } // namespace |
| 44 |
| 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), |
| 51 callback(callback), |
| 52 task_runner(task_runner), |
| 53 file_task_runner(file_task_runner) {} |
| 54 ~ConnectParams() {} |
| 55 |
| 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; |
| 61 }; |
| 62 |
| 63 class HidServiceLinux::Helper : public DeviceMonitorLinux::Observer, |
| 64 public base::MessageLoop::DestructionObserver { |
| 65 public: |
| 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); |
| 71 monitor->Enumerate( |
| 72 base::Bind(&Helper::OnDeviceAdded, base::Unretained(this))); |
| 73 } |
| 74 |
| 75 virtual ~Helper() { |
| 76 DCHECK(thread_checker_.CalledOnValidThread()); |
| 77 } |
| 78 |
| 79 private: |
| 80 // DeviceMonitorLinux::Observer: |
| 81 void OnDeviceAdded(udev_device* device) override { |
| 82 DCHECK(thread_checker_.CalledOnValidThread()); |
| 83 const char* device_path = udev_device_get_syspath(device); |
| 84 if (!device_path) { |
| 85 return; |
| 86 } |
| 87 const char* subsystem = udev_device_get_subsystem(device); |
| 88 if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) { |
| 89 return; |
| 90 } |
| 91 |
| 92 HidDeviceInfo device_info; |
| 93 device_info.device_id = device_path; |
| 94 |
| 95 const char* str_property = udev_device_get_devnode(device); |
| 96 if (!str_property) { |
| 97 return; |
| 98 } |
| 99 device_info.device_node = str_property; |
| 100 |
| 101 udev_device* parent = udev_device_get_parent(device); |
| 102 if (!parent) { |
| 103 return; |
| 104 } |
| 105 |
| 106 const char* hid_id = udev_device_get_property_value(parent, kHIDID); |
| 107 if (!hid_id) { |
| 108 return; |
| 109 } |
| 110 |
| 111 std::vector<std::string> parts; |
| 112 base::SplitString(hid_id, ':', &parts); |
| 113 if (parts.size() != 3) { |
| 114 return; |
| 115 } |
| 116 |
| 117 uint32_t int_property = 0; |
| 118 if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { |
| 119 device_info.vendor_id = int_property; |
| 120 } |
| 121 |
| 122 if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { |
| 123 device_info.product_id = int_property; |
| 124 } |
| 125 |
| 126 str_property = udev_device_get_property_value(parent, kHIDUnique); |
| 127 if (str_property != NULL) { |
| 128 device_info.serial_number = str_property; |
| 129 } |
| 130 |
| 131 str_property = udev_device_get_property_value(parent, kHIDName); |
| 132 if (str_property != NULL) { |
| 133 device_info.product_name = str_property; |
| 134 } |
| 135 |
| 136 const char* parent_sysfs_path = udev_device_get_syspath(parent); |
| 137 if (!parent_sysfs_path) { |
| 138 return; |
| 139 } |
| 140 base::FilePath report_descriptor_path = |
| 141 base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey); |
| 142 std::string report_descriptor_str; |
| 143 if (!base::ReadFileToString(report_descriptor_path, |
| 144 &report_descriptor_str)) { |
| 145 return; |
| 146 } |
| 147 |
| 148 HidReportDescriptor report_descriptor( |
| 149 reinterpret_cast<uint8_t*>(&report_descriptor_str[0]), |
| 150 report_descriptor_str.length()); |
| 151 report_descriptor.GetDetails( |
| 152 &device_info.collections, &device_info.has_report_id, |
| 153 &device_info.max_input_report_size, &device_info.max_output_report_size, |
| 154 &device_info.max_feature_report_size); |
| 155 |
| 156 task_runner_->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::AddDevice, |
| 157 service_, device_info)); |
| 158 } |
| 159 |
| 160 void OnDeviceRemoved(udev_device* device) override { |
| 161 DCHECK(thread_checker_.CalledOnValidThread()); |
| 162 const char* device_path = udev_device_get_syspath(device); |
| 163 if (device_path) { |
| 164 task_runner_->PostTask( |
| 165 FROM_HERE, |
| 166 base::Bind(&HidServiceLinux::RemoveDevice, service_, device_path)); |
| 167 } |
| 168 } |
| 169 |
| 170 // base::MessageLoop::DestructionObserver: |
| 171 void WillDestroyCurrentMessageLoop() override { |
| 172 DCHECK(thread_checker_.CalledOnValidThread()); |
| 173 base::MessageLoop::current()->RemoveDestructionObserver(this); |
| 174 delete this; |
| 175 } |
| 176 |
| 177 base::ThreadChecker thread_checker_; |
| 178 ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_; |
| 179 |
| 180 // This weak pointer is only valid when checked on this task runner. |
| 181 base::WeakPtr<HidServiceLinux> service_; |
| 182 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| 183 }; |
| 184 |
| 185 HidServiceLinux::HidServiceLinux( |
| 186 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) |
| 187 : file_task_runner_(file_task_runner), weak_factory_(this) { |
| 188 task_runner_ = base::ThreadTaskRunnerHandle::Get(); |
| 189 // The device watcher is passed a weak pointer back to this service so that it |
| 190 // can be cleaned up after the service is destroyed however this weak pointer |
| 191 // must be constructed on the this thread where it will be checked. |
| 192 file_task_runner_->PostTask( |
| 193 FROM_HERE, base::Bind(&HidServiceLinux::StartHelper, |
| 194 weak_factory_.GetWeakPtr(), task_runner_)); |
| 195 } |
| 196 |
| 197 // static |
| 198 void HidServiceLinux::StartHelper( |
| 199 base::WeakPtr<HidServiceLinux> weak_ptr, |
| 200 scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| 201 // Helper is a message loop destruction observer and will delete itself when |
| 202 // this thread's message loop is destroyed. |
| 203 new Helper(weak_ptr, task_runner); |
| 204 } |
| 205 |
| 206 void HidServiceLinux::Connect(const HidDeviceId& device_id, |
| 207 const ConnectCallback& callback) { |
| 208 DCHECK(thread_checker_.CalledOnValidThread()); |
| 209 |
| 210 const auto& map_entry = devices().find(device_id); |
| 211 if (map_entry == devices().end()) { |
| 212 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
| 213 return; |
| 214 } |
| 215 const HidDeviceInfo& device_info = map_entry->second; |
| 216 |
| 217 scoped_ptr<ConnectParams> params(new ConnectParams( |
| 218 device_info, callback, task_runner_, file_task_runner_)); |
| 219 |
41 #if defined(OS_CHROMEOS) | 220 #if defined(OS_CHROMEOS) |
42 void OnRequestAccessComplete( | |
43 scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner, | |
44 const base::Callback<void(bool success)>& callback, | |
45 bool success) { | |
46 reply_task_runner->PostTask(FROM_HERE, base::Bind(callback, success)); | |
47 } | |
48 | |
49 void RequestAccess( | |
50 const std::string& device_node, | |
51 scoped_refptr<base::SingleThreadTaskRunner> reply_task_runner, | |
52 const base::Callback<void(bool success)>& callback) { | |
53 bool success = false; | |
54 | |
55 if (base::SysInfo::IsRunningOnChromeOS()) { | 221 if (base::SysInfo::IsRunningOnChromeOS()) { |
56 chromeos::PermissionBrokerClient* client = | 222 chromeos::PermissionBrokerClient* client = |
57 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); | 223 chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); |
58 DCHECK(client) << "Could not get permission broker client."; | 224 DCHECK(client) << "Could not get permission broker client."; |
59 if (client) { | 225 if (client) { |
60 client->RequestPathAccess( | 226 client->RequestPathAccess( |
61 device_node, | 227 device_info.device_node, -1, |
62 -1, | 228 base::Bind(&HidServiceLinux::OnRequestPathAccessComplete, |
63 base::Bind(OnRequestAccessComplete, reply_task_runner, callback)); | 229 base::Passed(¶ms))); |
64 return; | 230 } else { |
65 } | 231 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); |
| 232 } |
| 233 return; |
| 234 } |
| 235 #endif // defined(OS_CHROMEOS) |
| 236 |
| 237 file_task_runner_->PostTask( |
| 238 FROM_HERE, |
| 239 base::Bind(&HidServiceLinux::OpenDevice, base::Passed(¶ms))); |
| 240 } |
| 241 |
| 242 HidServiceLinux::~HidServiceLinux() { |
| 243 file_task_runner_->DeleteSoon(FROM_HERE, helper_.release()); |
| 244 } |
| 245 |
| 246 #if defined(OS_CHROMEOS) |
| 247 // static |
| 248 void HidServiceLinux::OnRequestPathAccessComplete( |
| 249 scoped_ptr<ConnectParams> params, |
| 250 bool success) { |
| 251 if (success) { |
| 252 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner = |
| 253 params->file_task_runner; |
| 254 file_task_runner->PostTask( |
| 255 FROM_HERE, |
| 256 base::Bind(&HidServiceLinux::OpenDevice, base::Passed(¶ms))); |
66 } else { | 257 } else { |
67 // Not really running on Chrome OS, declare success. | 258 params->callback.Run(nullptr); |
68 success = true; | 259 } |
69 } | 260 } |
70 | 261 #endif // defined(OS_CHROMEOS) |
71 reply_task_runner->PostTask(FROM_HERE, base::Bind(callback, success)); | 262 |
72 } | 263 // static |
73 #endif | 264 void HidServiceLinux::OpenDevice(scoped_ptr<ConnectParams> params) { |
74 | |
75 } // namespace | |
76 | |
77 HidServiceLinux::HidServiceLinux( | |
78 scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) | |
79 : ui_task_runner_(ui_task_runner), | |
80 weak_factory_(this) { | |
81 base::ThreadRestrictions::AssertIOAllowed(); | 265 base::ThreadRestrictions::AssertIOAllowed(); |
82 task_runner_ = base::ThreadTaskRunnerHandle::Get(); | 266 scoped_refptr<base::SingleThreadTaskRunner> task_runner = params->task_runner; |
83 DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance(); | 267 base::FilePath device_path(params->device_info.device_node); |
84 monitor->AddObserver(this); | 268 base::File& device_file = params->device_file; |
85 monitor->Enumerate( | 269 int flags = |
86 base::Bind(&HidServiceLinux::OnDeviceAdded, weak_factory_.GetWeakPtr())); | 270 base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE; |
87 } | 271 device_file.Initialize(device_path, flags); |
88 | 272 if (!device_file.IsValid()) { |
89 void HidServiceLinux::Connect(const HidDeviceId& device_id, | 273 base::File::Error file_error = device_file.error_details(); |
90 const ConnectCallback& callback) { | 274 |
91 DCHECK(thread_checker_.CalledOnValidThread()); | 275 if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) { |
92 | 276 VLOG(1) << "Access denied opening device read-write, trying read-only."; |
93 ScopedUdevDevicePtr device = | 277 flags = base::File::FLAG_OPEN | base::File::FLAG_READ; |
94 DeviceMonitorLinux::GetInstance()->GetDeviceFromPath( | 278 device_file.Initialize(device_path, flags); |
95 device_id); | 279 } |
96 if (!device) { | 280 } |
97 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); | 281 if (!device_file.IsValid()) { |
98 return; | 282 LOG(ERROR) << "Failed to open '" << params->device_info.device_node << "': " |
99 } | 283 << base::File::ErrorToString(device_file.error_details()); |
100 | 284 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
101 const char* device_node = udev_device_get_devnode(device.get()); | 285 return; |
102 if (!device_node) { | 286 } |
103 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); | 287 |
104 return; | 288 int result = fcntl(device_file.GetPlatformFile(), F_GETFL); |
105 } | 289 if (result == -1) { |
106 | 290 PLOG(ERROR) << "Failed to get flags from the device file descriptor"; |
107 base::Callback<void(bool success)> finish_connect = | 291 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
108 base::Bind(&HidServiceLinux::FinishConnect, | 292 return; |
109 weak_factory_.GetWeakPtr(), | 293 } |
110 device_id, | 294 |
111 std::string(device_node), | 295 result = fcntl(device_file.GetPlatformFile(), F_SETFL, result | O_NONBLOCK); |
112 callback); | 296 if (result == -1) { |
113 | 297 PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd"; |
114 #if defined(OS_CHROMEOS) | 298 task_runner->PostTask(FROM_HERE, base::Bind(params->callback, nullptr)); |
115 ui_task_runner_->PostTask(FROM_HERE, | 299 return; |
116 base::Bind(RequestAccess, | 300 } |
117 std::string(device_node), | 301 |
118 task_runner_, | 302 task_runner->PostTask(FROM_HERE, base::Bind(&HidServiceLinux::ConnectImpl, |
119 finish_connect)); | 303 base::Passed(¶ms))); |
120 #else | 304 } |
121 // Use the task runner to preserve the asynchronous behavior of this call on | 305 |
122 // non-Chrome OS platforms. | 306 // static |
123 task_runner_->PostTask(FROM_HERE, base::Bind(finish_connect, true)); | 307 void HidServiceLinux::ConnectImpl(scoped_ptr<ConnectParams> params) { |
124 #endif | 308 DCHECK(params->device_file.IsValid()); |
125 } | 309 params->callback.Run(make_scoped_refptr( |
126 | 310 new HidConnectionLinux(params->device_info, params->device_file.Pass(), |
127 HidServiceLinux::~HidServiceLinux() { | 311 params->file_task_runner))); |
128 if (DeviceMonitorLinux::HasInstance()) | |
129 DeviceMonitorLinux::GetInstance()->RemoveObserver(this); | |
130 } | |
131 | |
132 void HidServiceLinux::OnDeviceAdded(udev_device* device) { | |
133 if (!device) | |
134 return; | |
135 | |
136 const char* device_path = udev_device_get_syspath(device); | |
137 if (!device_path) | |
138 return; | |
139 const char* subsystem = udev_device_get_subsystem(device); | |
140 if (!subsystem || strcmp(subsystem, kHidrawSubsystem) != 0) | |
141 return; | |
142 | |
143 HidDeviceInfo device_info; | |
144 device_info.device_id = device_path; | |
145 | |
146 uint32_t int_property = 0; | |
147 const char* str_property = NULL; | |
148 | |
149 udev_device* parent = udev_device_get_parent(device); | |
150 if (!parent) { | |
151 return; | |
152 } | |
153 | |
154 const char* hid_id = udev_device_get_property_value(parent, kHIDID); | |
155 if (!hid_id) { | |
156 return; | |
157 } | |
158 | |
159 std::vector<std::string> parts; | |
160 base::SplitString(hid_id, ':', &parts); | |
161 if (parts.size() != 3) { | |
162 return; | |
163 } | |
164 | |
165 if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { | |
166 device_info.vendor_id = int_property; | |
167 } | |
168 | |
169 if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { | |
170 device_info.product_id = int_property; | |
171 } | |
172 | |
173 str_property = udev_device_get_property_value(parent, kHIDUnique); | |
174 if (str_property != NULL) { | |
175 device_info.serial_number = str_property; | |
176 } | |
177 | |
178 str_property = udev_device_get_property_value(parent, kHIDName); | |
179 if (str_property != NULL) { | |
180 device_info.product_name = str_property; | |
181 } | |
182 | |
183 const char* parent_sysfs_path = udev_device_get_syspath(parent); | |
184 if (!parent_sysfs_path) { | |
185 return; | |
186 } | |
187 base::FilePath report_descriptor_path = | |
188 base::FilePath(parent_sysfs_path).Append(kSysfsReportDescriptorKey); | |
189 std::string report_descriptor_str; | |
190 if (!base::ReadFileToString(report_descriptor_path, &report_descriptor_str)) { | |
191 return; | |
192 } | |
193 | |
194 HidReportDescriptor report_descriptor( | |
195 reinterpret_cast<uint8_t*>(&report_descriptor_str[0]), | |
196 report_descriptor_str.length()); | |
197 report_descriptor.GetDetails(&device_info.collections, | |
198 &device_info.has_report_id, | |
199 &device_info.max_input_report_size, | |
200 &device_info.max_output_report_size, | |
201 &device_info.max_feature_report_size); | |
202 | |
203 AddDevice(device_info); | |
204 } | |
205 | |
206 void HidServiceLinux::OnDeviceRemoved(udev_device* device) { | |
207 const char* device_path = udev_device_get_syspath(device);; | |
208 if (device_path) { | |
209 RemoveDevice(device_path); | |
210 } | |
211 } | |
212 | |
213 void HidServiceLinux::FinishConnect( | |
214 const HidDeviceId& device_id, | |
215 const std::string device_node, | |
216 const base::Callback<void(scoped_refptr<HidConnection>)>& callback, | |
217 bool success) { | |
218 DCHECK(thread_checker_.CalledOnValidThread()); | |
219 if (!success) { | |
220 callback.Run(nullptr); | |
221 } | |
222 | |
223 const auto& map_entry = devices().find(device_id); | |
224 if (map_entry == devices().end()) { | |
225 callback.Run(nullptr); | |
226 } | |
227 | |
228 callback.Run(new HidConnectionLinux(map_entry->second, device_node)); | |
229 } | 312 } |
230 | 313 |
231 } // namespace device | 314 } // namespace device |
OLD | NEW |