Update Smart lock strings in chrome://settings.
[chromium-blink-merge.git] / device / hid / hid_service_mac.cc
blob6e091a851e7da88fb94950558f82520755e3591e
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_mac.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <IOKit/hid/IOHIDDevice.h>
10 #include <set>
11 #include <string>
12 #include <vector>
14 #include "base/bind.h"
15 #include "base/location.h"
16 #include "base/logging.h"
17 #include "base/mac/foundation_util.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/stl_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "base/thread_task_runner_handle.h"
23 #include "base/threading/thread_restrictions.h"
24 #include "device/hid/hid_connection_mac.h"
26 namespace device {
28 namespace {
30 bool TryGetHidIntProperty(IOHIDDeviceRef device,
31 CFStringRef key,
32 int32_t* result) {
33 CFNumberRef ref =
34 base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
35 return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
38 int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) {
39 int32_t value;
40 if (TryGetHidIntProperty(device, key, &value))
41 return value;
42 return 0;
45 bool TryGetHidStringProperty(IOHIDDeviceRef device,
46 CFStringRef key,
47 std::string* result) {
48 CFStringRef ref =
49 base::mac::CFCast<CFStringRef>(IOHIDDeviceGetProperty(device, key));
50 if (!ref) {
51 return false;
53 *result = base::SysCFStringRefToUTF8(ref);
54 return true;
57 std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) {
58 std::string value;
59 TryGetHidStringProperty(device, key, &value);
60 return value;
63 void GetReportIds(IOHIDElementRef element, std::set<int>* reportIDs) {
64 uint32_t reportID = IOHIDElementGetReportID(element);
65 if (reportID) {
66 reportIDs->insert(reportID);
69 CFArrayRef children = IOHIDElementGetChildren(element);
70 if (!children) {
71 return;
74 CFIndex childrenCount = CFArrayGetCount(children);
75 for (CFIndex j = 0; j < childrenCount; ++j) {
76 const IOHIDElementRef child = static_cast<IOHIDElementRef>(
77 const_cast<void*>(CFArrayGetValueAtIndex(children, j)));
78 GetReportIds(child, reportIDs);
82 void GetCollectionInfos(IOHIDDeviceRef device,
83 bool* has_report_id,
84 std::vector<HidCollectionInfo>* top_level_collections) {
85 STLClearObject(top_level_collections);
86 CFMutableDictionaryRef collections_filter =
87 CFDictionaryCreateMutable(kCFAllocatorDefault,
89 &kCFTypeDictionaryKeyCallBacks,
90 &kCFTypeDictionaryValueCallBacks);
91 const int kCollectionTypeValue = kIOHIDElementTypeCollection;
92 CFNumberRef collection_type_id = CFNumberCreate(
93 kCFAllocatorDefault, kCFNumberIntType, &kCollectionTypeValue);
94 CFDictionarySetValue(
95 collections_filter, CFSTR(kIOHIDElementTypeKey), collection_type_id);
96 CFRelease(collection_type_id);
97 CFArrayRef collections = IOHIDDeviceCopyMatchingElements(
98 device, collections_filter, kIOHIDOptionsTypeNone);
99 CFIndex collectionsCount = CFArrayGetCount(collections);
100 *has_report_id = false;
101 for (CFIndex i = 0; i < collectionsCount; i++) {
102 const IOHIDElementRef collection = static_cast<IOHIDElementRef>(
103 const_cast<void*>(CFArrayGetValueAtIndex(collections, i)));
104 // Top-Level Collection has no parent
105 if (IOHIDElementGetParent(collection) == 0) {
106 HidCollectionInfo collection_info;
107 HidUsageAndPage::Page page = static_cast<HidUsageAndPage::Page>(
108 IOHIDElementGetUsagePage(collection));
109 uint32_t usage = IOHIDElementGetUsage(collection);
110 if (usage > std::numeric_limits<uint16_t>::max())
111 continue;
112 collection_info.usage =
113 HidUsageAndPage(static_cast<uint16_t>(usage), page);
114 // Explore children recursively and retrieve their report IDs
115 GetReportIds(collection, &collection_info.report_ids);
116 if (collection_info.report_ids.size() > 0) {
117 *has_report_id = true;
119 top_level_collections->push_back(collection_info);
124 void PopulateDeviceInfo(io_service_t service, HidDeviceInfo* device_info) {
125 io_string_t service_path;
126 IOReturn result =
127 IORegistryEntryGetPath(service, kIOServicePlane, service_path);
128 if (result != kIOReturnSuccess) {
129 VLOG(1) << "Failed to get IOService path: "
130 << base::StringPrintf("0x%04x", result);
131 return;
134 base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
135 IOHIDDeviceCreate(kCFAllocatorDefault, service));
136 device_info->device_id = service_path;
137 device_info->vendor_id =
138 GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey));
139 device_info->product_id =
140 GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey));
141 device_info->product_name =
142 GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey));
143 device_info->serial_number =
144 GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey));
145 GetCollectionInfos(
146 hid_device, &device_info->has_report_id, &device_info->collections);
147 device_info->max_input_report_size =
148 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey));
149 if (device_info->has_report_id && device_info->max_input_report_size > 0) {
150 device_info->max_input_report_size--;
152 device_info->max_output_report_size =
153 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey));
154 if (device_info->has_report_id && device_info->max_output_report_size > 0) {
155 device_info->max_output_report_size--;
157 device_info->max_feature_report_size =
158 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey));
159 if (device_info->has_report_id && device_info->max_feature_report_size > 0) {
160 device_info->max_feature_report_size--;
164 } // namespace
166 HidServiceMac::HidServiceMac(
167 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
168 : file_task_runner_(file_task_runner) {
169 task_runner_ = base::ThreadTaskRunnerHandle::Get();
170 DCHECK(task_runner_.get());
172 notify_port_ = IONotificationPortCreate(kIOMasterPortDefault);
173 CFRunLoopAddSource(CFRunLoopGetMain(),
174 IONotificationPortGetRunLoopSource(notify_port_),
175 kCFRunLoopDefaultMode);
177 io_iterator_t iterator;
178 IOReturn result =
179 IOServiceAddMatchingNotification(notify_port_,
180 kIOFirstMatchNotification,
181 IOServiceMatching(kIOHIDDeviceKey),
182 FirstMatchCallback,
183 this,
184 &iterator);
185 if (result != kIOReturnSuccess) {
186 LOG(ERROR) << "Failed to listen for device arrival: "
187 << base::StringPrintf("0x%04x", result);
188 return;
191 // Drain the iterator to arm the notification.
192 devices_added_iterator_.reset(iterator);
193 AddDevices();
194 iterator = IO_OBJECT_NULL;
196 result = IOServiceAddMatchingNotification(notify_port_,
197 kIOTerminatedNotification,
198 IOServiceMatching(kIOHIDDeviceKey),
199 TerminatedCallback,
200 this,
201 &iterator);
202 if (result != kIOReturnSuccess) {
203 LOG(ERROR) << "Failed to listen for device removal: "
204 << base::StringPrintf("0x%04x", result);
205 return;
208 // Drain devices_added_iterator_ to arm the notification.
209 devices_removed_iterator_.reset(iterator);
210 RemoveDevices();
213 HidServiceMac::~HidServiceMac() {
216 // static
217 void HidServiceMac::FirstMatchCallback(void* context, io_iterator_t iterator) {
218 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
219 HidServiceMac* service = static_cast<HidServiceMac*>(context);
220 DCHECK_EQ(service->devices_added_iterator_, iterator);
221 service->AddDevices();
224 // static
225 void HidServiceMac::TerminatedCallback(void* context, io_iterator_t iterator) {
226 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
227 HidServiceMac* service = static_cast<HidServiceMac*>(context);
228 DCHECK_EQ(service->devices_removed_iterator_, iterator);
229 service->RemoveDevices();
232 void HidServiceMac::AddDevices() {
233 DCHECK(thread_checker_.CalledOnValidThread());
235 io_service_t device;
236 while ((device = IOIteratorNext(devices_added_iterator_)) != IO_OBJECT_NULL) {
237 HidDeviceInfo device_info;
238 PopulateDeviceInfo(device, &device_info);
239 AddDevice(device_info);
240 // The reference retained by IOIteratorNext is released below in
241 // RemoveDevices when the device is removed.
245 void HidServiceMac::RemoveDevices() {
246 DCHECK(thread_checker_.CalledOnValidThread());
248 io_service_t device;
249 while ((device = IOIteratorNext(devices_removed_iterator_)) !=
250 IO_OBJECT_NULL) {
251 io_string_t service_path;
252 IOReturn result =
253 IORegistryEntryGetPath(device, kIOServicePlane, service_path);
254 if (result == kIOReturnSuccess) {
255 RemoveDevice(service_path);
258 // Release reference retained by AddDevices above.
259 IOObjectRelease(device);
260 // Release the reference retained by IOIteratorNext.
261 IOObjectRelease(device);
265 void HidServiceMac::Connect(const HidDeviceId& device_id,
266 const ConnectCallback& callback) {
267 DCHECK(thread_checker_.CalledOnValidThread());
269 const auto& map_entry = devices().find(device_id);
270 if (map_entry == devices().end()) {
271 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
272 return;
274 const HidDeviceInfo& device_info = map_entry->second;
276 io_string_t service_path;
277 strncpy(service_path, device_id.c_str(), sizeof service_path);
278 base::mac::ScopedIOObject<io_service_t> service(
279 IORegistryEntryFromPath(kIOMasterPortDefault, service_path));
280 if (!service.get()) {
281 VLOG(1) << "IOService not found for path: " << device_id;
282 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
283 return;
286 base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
287 IOHIDDeviceCreate(kCFAllocatorDefault, service));
288 IOReturn result = IOHIDDeviceOpen(hid_device, kIOHIDOptionsTypeNone);
289 if (result != kIOReturnSuccess) {
290 VLOG(1) << "Failed to open device: "
291 << base::StringPrintf("0x%04x", result);
292 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
293 return;
296 task_runner_->PostTask(
297 FROM_HERE,
298 base::Bind(callback,
299 make_scoped_refptr(new HidConnectionMac(
300 hid_device.release(), device_info, file_task_runner_))));
303 } // namespace device