mac: Let IPhotoDataProvider::GetAlbumNames() return albums in a deterministic order.
[chromium-blink-merge.git] / device / hid / hid_service_mac.cc
blobbabb91f348d0cd88d46fb20b66aace3fb7571fce
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 "components/device_event_log/device_event_log.h"
25 #include "device/hid/hid_connection_mac.h"
27 namespace device {
29 namespace {
31 std::string HexErrorCode(IOReturn error_code) {
32 return base::StringPrintf("0x%04x", error_code);
35 bool TryGetHidIntProperty(IOHIDDeviceRef device,
36 CFStringRef key,
37 int32_t* result) {
38 CFNumberRef ref =
39 base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
40 return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
43 int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) {
44 int32_t value;
45 if (TryGetHidIntProperty(device, key, &value))
46 return value;
47 return 0;
50 bool TryGetHidStringProperty(IOHIDDeviceRef device,
51 CFStringRef key,
52 std::string* result) {
53 CFStringRef ref =
54 base::mac::CFCast<CFStringRef>(IOHIDDeviceGetProperty(device, key));
55 if (!ref) {
56 return false;
58 *result = base::SysCFStringRefToUTF8(ref);
59 return true;
62 std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) {
63 std::string value;
64 TryGetHidStringProperty(device, key, &value);
65 return value;
68 bool TryGetHidDataProperty(IOHIDDeviceRef device,
69 CFStringRef key,
70 std::vector<uint8>* result) {
71 CFDataRef ref =
72 base::mac::CFCast<CFDataRef>(IOHIDDeviceGetProperty(device, key));
73 if (!ref) {
74 return false;
76 STLClearObject(result);
77 const uint8* bytes = CFDataGetBytePtr(ref);
78 result->insert(result->begin(), bytes, bytes + CFDataGetLength(ref));
79 return true;
82 } // namespace
84 HidServiceMac::HidServiceMac(
85 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
86 : file_task_runner_(file_task_runner) {
87 task_runner_ = base::ThreadTaskRunnerHandle::Get();
88 DCHECK(task_runner_.get());
90 notify_port_ = IONotificationPortCreate(kIOMasterPortDefault);
91 CFRunLoopAddSource(CFRunLoopGetMain(),
92 IONotificationPortGetRunLoopSource(notify_port_),
93 kCFRunLoopDefaultMode);
95 io_iterator_t iterator;
96 IOReturn result =
97 IOServiceAddMatchingNotification(notify_port_,
98 kIOFirstMatchNotification,
99 IOServiceMatching(kIOHIDDeviceKey),
100 FirstMatchCallback,
101 this,
102 &iterator);
103 if (result != kIOReturnSuccess) {
104 HID_LOG(ERROR) << "Failed to listen for device arrival: "
105 << HexErrorCode(result);
106 return;
109 // Drain the iterator to arm the notification.
110 devices_added_iterator_.reset(iterator);
111 AddDevices();
112 iterator = IO_OBJECT_NULL;
114 result = IOServiceAddMatchingNotification(notify_port_,
115 kIOTerminatedNotification,
116 IOServiceMatching(kIOHIDDeviceKey),
117 TerminatedCallback,
118 this,
119 &iterator);
120 if (result != kIOReturnSuccess) {
121 HID_LOG(ERROR) << "Failed to listen for device removal: "
122 << HexErrorCode(result);
123 return;
126 // Drain devices_added_iterator_ to arm the notification.
127 devices_removed_iterator_.reset(iterator);
128 RemoveDevices();
129 FirstEnumerationComplete();
132 HidServiceMac::~HidServiceMac() {}
134 void HidServiceMac::Connect(const HidDeviceId& device_id,
135 const ConnectCallback& callback) {
136 DCHECK(thread_checker_.CalledOnValidThread());
138 const auto& map_entry = devices().find(device_id);
139 if (map_entry == devices().end()) {
140 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
141 return;
143 scoped_refptr<HidDeviceInfo> device_info = map_entry->second;
145 base::ScopedCFTypeRef<CFDictionaryRef> matching_dict(
146 IORegistryEntryIDMatching(device_id));
147 if (!matching_dict.get()) {
148 HID_LOG(EVENT) << "Failed to create matching dictionary for ID: "
149 << device_id;
150 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
151 return;
154 // IOServiceGetMatchingService consumes a reference to the matching dictionary
155 // passed to it.
156 base::mac::ScopedIOObject<io_service_t> service(IOServiceGetMatchingService(
157 kIOMasterPortDefault, matching_dict.release()));
158 if (!service.get()) {
159 HID_LOG(EVENT) << "IOService not found for ID: " << device_id;
160 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
161 return;
164 base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
165 IOHIDDeviceCreate(kCFAllocatorDefault, service));
166 if (!hid_device) {
167 HID_LOG(EVENT) << "Unable to create IOHIDDevice object.";
168 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
169 return;
172 IOReturn result = IOHIDDeviceOpen(hid_device, kIOHIDOptionsTypeNone);
173 if (result != kIOReturnSuccess) {
174 HID_LOG(EVENT) << "Failed to open device: " << HexErrorCode(result);
175 task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr));
176 return;
179 task_runner_->PostTask(
180 FROM_HERE, base::Bind(callback, make_scoped_refptr(new HidConnectionMac(
181 hid_device.release(), device_info,
182 file_task_runner_))));
185 // static
186 void HidServiceMac::FirstMatchCallback(void* context, io_iterator_t iterator) {
187 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
188 HidServiceMac* service = static_cast<HidServiceMac*>(context);
189 DCHECK_EQ(service->devices_added_iterator_, iterator);
190 service->AddDevices();
193 // static
194 void HidServiceMac::TerminatedCallback(void* context, io_iterator_t iterator) {
195 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
196 HidServiceMac* service = static_cast<HidServiceMac*>(context);
197 DCHECK_EQ(service->devices_removed_iterator_, iterator);
198 service->RemoveDevices();
201 void HidServiceMac::AddDevices() {
202 DCHECK(thread_checker_.CalledOnValidThread());
204 io_service_t device;
205 while ((device = IOIteratorNext(devices_added_iterator_)) != IO_OBJECT_NULL) {
206 scoped_refptr<HidDeviceInfo> device_info = CreateDeviceInfo(device);
207 if (device_info) {
208 AddDevice(device_info);
209 // The reference retained by IOIteratorNext is released below in
210 // RemoveDevices when the device is removed.
211 } else {
212 IOObjectRelease(device);
217 void HidServiceMac::RemoveDevices() {
218 DCHECK(thread_checker_.CalledOnValidThread());
220 io_service_t device;
221 while ((device = IOIteratorNext(devices_removed_iterator_)) !=
222 IO_OBJECT_NULL) {
223 uint64_t entry_id;
224 IOReturn result = IORegistryEntryGetRegistryEntryID(device, &entry_id);
225 if (result == kIOReturnSuccess) {
226 RemoveDevice(entry_id);
229 // Release reference retained by AddDevices above.
230 IOObjectRelease(device);
231 // Release the reference retained by IOIteratorNext.
232 IOObjectRelease(device);
236 // static
237 scoped_refptr<HidDeviceInfo> HidServiceMac::CreateDeviceInfo(
238 io_service_t service) {
239 uint64_t entry_id;
240 IOReturn result = IORegistryEntryGetRegistryEntryID(service, &entry_id);
241 if (result != kIOReturnSuccess) {
242 HID_LOG(EVENT) << "Failed to get IORegistryEntry ID: "
243 << HexErrorCode(result);
244 return nullptr;
247 base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
248 IOHIDDeviceCreate(kCFAllocatorDefault, service));
249 if (!hid_device) {
250 HID_LOG(EVENT) << "Unable to create IOHIDDevice object for new device.";
251 return nullptr;
254 std::vector<uint8> report_descriptor;
255 if (!TryGetHidDataProperty(hid_device, CFSTR(kIOHIDReportDescriptorKey),
256 &report_descriptor)) {
257 HID_LOG(EVENT) << "Unable to get report descriptor for new device.";
258 return nullptr;
261 return new HidDeviceInfo(
262 entry_id, GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey)),
263 GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey)),
264 GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey)),
265 GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey)),
266 kHIDBusTypeUSB, // TODO(reillyg): Detect Bluetooth. crbug.com/443335
267 report_descriptor);
270 } // namespace device