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