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>
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"
31 std::string
HexErrorCode(IOReturn error_code
) {
32 return base::StringPrintf("0x%04x", error_code
);
35 bool TryGetHidIntProperty(IOHIDDeviceRef device
,
39 base::mac::CFCast
<CFNumberRef
>(IOHIDDeviceGetProperty(device
, key
));
40 return ref
&& CFNumberGetValue(ref
, kCFNumberSInt32Type
, result
);
43 int32_t GetHidIntProperty(IOHIDDeviceRef device
, CFStringRef key
) {
45 if (TryGetHidIntProperty(device
, key
, &value
))
50 bool TryGetHidStringProperty(IOHIDDeviceRef device
,
52 std::string
* result
) {
54 base::mac::CFCast
<CFStringRef
>(IOHIDDeviceGetProperty(device
, key
));
58 *result
= base::SysCFStringRefToUTF8(ref
);
62 std::string
GetHidStringProperty(IOHIDDeviceRef device
, CFStringRef key
) {
64 TryGetHidStringProperty(device
, key
, &value
);
68 bool TryGetHidDataProperty(IOHIDDeviceRef device
,
70 std::vector
<uint8
>* result
) {
72 base::mac::CFCast
<CFDataRef
>(IOHIDDeviceGetProperty(device
, key
));
76 STLClearObject(result
);
77 const uint8
* bytes
= CFDataGetBytePtr(ref
);
78 result
->insert(result
->begin(), bytes
, bytes
+ CFDataGetLength(ref
));
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
;
97 IOServiceAddMatchingNotification(notify_port_
,
98 kIOFirstMatchNotification
,
99 IOServiceMatching(kIOHIDDeviceKey
),
103 if (result
!= kIOReturnSuccess
) {
104 HID_LOG(ERROR
) << "Failed to listen for device arrival: "
105 << HexErrorCode(result
);
109 // Drain the iterator to arm the notification.
110 devices_added_iterator_
.reset(iterator
);
112 iterator
= IO_OBJECT_NULL
;
114 result
= IOServiceAddMatchingNotification(notify_port_
,
115 kIOTerminatedNotification
,
116 IOServiceMatching(kIOHIDDeviceKey
),
120 if (result
!= kIOReturnSuccess
) {
121 HID_LOG(ERROR
) << "Failed to listen for device removal: "
122 << HexErrorCode(result
);
126 // Drain devices_added_iterator_ to arm the notification.
127 devices_removed_iterator_
.reset(iterator
);
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));
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: "
150 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
154 // IOServiceGetMatchingService consumes a reference to the matching dictionary
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));
164 base::ScopedCFTypeRef
<IOHIDDeviceRef
> hid_device(
165 IOHIDDeviceCreate(kCFAllocatorDefault
, service
));
167 HID_LOG(EVENT
) << "Unable to create IOHIDDevice object.";
168 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
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));
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_
))));
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();
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());
205 while ((device
= IOIteratorNext(devices_added_iterator_
)) != IO_OBJECT_NULL
) {
206 scoped_refptr
<HidDeviceInfo
> device_info
= CreateDeviceInfo(device
);
208 AddDevice(device_info
);
209 // The reference retained by IOIteratorNext is released below in
210 // RemoveDevices when the device is removed.
212 IOObjectRelease(device
);
217 void HidServiceMac::RemoveDevices() {
218 DCHECK(thread_checker_
.CalledOnValidThread());
221 while ((device
= IOIteratorNext(devices_removed_iterator_
)) !=
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
);
237 scoped_refptr
<HidDeviceInfo
> HidServiceMac::CreateDeviceInfo(
238 io_service_t service
) {
240 IOReturn result
= IORegistryEntryGetRegistryEntryID(service
, &entry_id
);
241 if (result
!= kIOReturnSuccess
) {
242 HID_LOG(EVENT
) << "Failed to get IORegistryEntry ID: "
243 << HexErrorCode(result
);
247 base::ScopedCFTypeRef
<IOHIDDeviceRef
> hid_device(
248 IOHIDDeviceCreate(kCFAllocatorDefault
, service
));
250 HID_LOG(EVENT
) << "Unable to create IOHIDDevice object for new device.";
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.";
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
270 } // namespace device