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 "device/hid/hid_connection_mac.h"
30 bool TryGetHidIntProperty(IOHIDDeviceRef device
,
34 base::mac::CFCast
<CFNumberRef
>(IOHIDDeviceGetProperty(device
, key
));
35 return ref
&& CFNumberGetValue(ref
, kCFNumberSInt32Type
, result
);
38 int32_t GetHidIntProperty(IOHIDDeviceRef device
, CFStringRef key
) {
40 if (TryGetHidIntProperty(device
, key
, &value
))
45 bool TryGetHidStringProperty(IOHIDDeviceRef device
,
47 std::string
* result
) {
49 base::mac::CFCast
<CFStringRef
>(IOHIDDeviceGetProperty(device
, key
));
53 *result
= base::SysCFStringRefToUTF8(ref
);
57 std::string
GetHidStringProperty(IOHIDDeviceRef device
, CFStringRef key
) {
59 TryGetHidStringProperty(device
, key
, &value
);
63 bool TryGetHidDataProperty(IOHIDDeviceRef device
,
65 std::vector
<uint8
>* result
) {
67 base::mac::CFCast
<CFDataRef
>(IOHIDDeviceGetProperty(device
, key
));
71 STLClearObject(result
);
72 const uint8
* bytes
= CFDataGetBytePtr(ref
);
73 result
->insert(result
->begin(), bytes
, bytes
+ CFDataGetLength(ref
));
79 HidServiceMac::HidServiceMac(
80 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
81 : file_task_runner_(file_task_runner
) {
82 task_runner_
= base::ThreadTaskRunnerHandle::Get();
83 DCHECK(task_runner_
.get());
85 notify_port_
= IONotificationPortCreate(kIOMasterPortDefault
);
86 CFRunLoopAddSource(CFRunLoopGetMain(),
87 IONotificationPortGetRunLoopSource(notify_port_
),
88 kCFRunLoopDefaultMode
);
90 io_iterator_t iterator
;
92 IOServiceAddMatchingNotification(notify_port_
,
93 kIOFirstMatchNotification
,
94 IOServiceMatching(kIOHIDDeviceKey
),
98 if (result
!= kIOReturnSuccess
) {
99 LOG(ERROR
) << "Failed to listen for device arrival: "
100 << base::StringPrintf("0x%04x", result
);
104 // Drain the iterator to arm the notification.
105 devices_added_iterator_
.reset(iterator
);
107 iterator
= IO_OBJECT_NULL
;
109 result
= IOServiceAddMatchingNotification(notify_port_
,
110 kIOTerminatedNotification
,
111 IOServiceMatching(kIOHIDDeviceKey
),
115 if (result
!= kIOReturnSuccess
) {
116 LOG(ERROR
) << "Failed to listen for device removal: "
117 << base::StringPrintf("0x%04x", result
);
121 // Drain devices_added_iterator_ to arm the notification.
122 devices_removed_iterator_
.reset(iterator
);
124 FirstEnumerationComplete();
127 void HidServiceMac::Connect(const HidDeviceId
& device_id
,
128 const ConnectCallback
& callback
) {
129 DCHECK(thread_checker_
.CalledOnValidThread());
131 const auto& map_entry
= devices().find(device_id
);
132 if (map_entry
== devices().end()) {
133 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
136 scoped_refptr
<HidDeviceInfo
> device_info
= map_entry
->second
;
138 io_string_t service_path
;
139 strncpy(service_path
, device_id
.c_str(), sizeof service_path
);
140 base::mac::ScopedIOObject
<io_service_t
> service(
141 IORegistryEntryFromPath(kIOMasterPortDefault
, service_path
));
142 if (!service
.get()) {
143 VLOG(1) << "IOService not found for path: " << device_id
;
144 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
148 base::ScopedCFTypeRef
<IOHIDDeviceRef
> hid_device(
149 IOHIDDeviceCreate(kCFAllocatorDefault
, service
));
151 VLOG(1) << "Unable to create IOHIDDevice object.";
152 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
156 IOReturn result
= IOHIDDeviceOpen(hid_device
, kIOHIDOptionsTypeNone
);
157 if (result
!= kIOReturnSuccess
) {
158 VLOG(1) << "Failed to open device: " << base::StringPrintf("0x%04x",
160 task_runner_
->PostTask(FROM_HERE
, base::Bind(callback
, nullptr));
164 task_runner_
->PostTask(
165 FROM_HERE
, base::Bind(callback
, make_scoped_refptr(new HidConnectionMac(
166 hid_device
.release(), device_info
,
167 file_task_runner_
))));
170 HidServiceMac::~HidServiceMac() {
174 void HidServiceMac::FirstMatchCallback(void* context
, io_iterator_t iterator
) {
175 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
176 HidServiceMac
* service
= static_cast<HidServiceMac
*>(context
);
177 DCHECK_EQ(service
->devices_added_iterator_
, iterator
);
178 service
->AddDevices();
182 void HidServiceMac::TerminatedCallback(void* context
, io_iterator_t iterator
) {
183 DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
184 HidServiceMac
* service
= static_cast<HidServiceMac
*>(context
);
185 DCHECK_EQ(service
->devices_removed_iterator_
, iterator
);
186 service
->RemoveDevices();
189 void HidServiceMac::AddDevices() {
190 DCHECK(thread_checker_
.CalledOnValidThread());
193 while ((device
= IOIteratorNext(devices_added_iterator_
)) != IO_OBJECT_NULL
) {
194 scoped_refptr
<HidDeviceInfo
> device_info
= CreateDeviceInfo(device
);
196 AddDevice(device_info
);
197 // The reference retained by IOIteratorNext is released below in
198 // RemoveDevices when the device is removed.
200 IOObjectRelease(device
);
205 void HidServiceMac::RemoveDevices() {
206 DCHECK(thread_checker_
.CalledOnValidThread());
209 while ((device
= IOIteratorNext(devices_removed_iterator_
)) !=
211 io_string_t service_path
;
213 IORegistryEntryGetPath(device
, kIOServicePlane
, service_path
);
214 if (result
== kIOReturnSuccess
) {
215 RemoveDevice(service_path
);
218 // Release reference retained by AddDevices above.
219 IOObjectRelease(device
);
220 // Release the reference retained by IOIteratorNext.
221 IOObjectRelease(device
);
226 scoped_refptr
<HidDeviceInfo
> HidServiceMac::CreateDeviceInfo(
227 io_service_t service
) {
228 io_string_t service_path
;
230 IORegistryEntryGetPath(service
, kIOServicePlane
, service_path
);
231 if (result
!= kIOReturnSuccess
) {
232 VLOG(1) << "Failed to get IOService path: " << base::StringPrintf("0x%04x",
237 base::ScopedCFTypeRef
<IOHIDDeviceRef
> hid_device(
238 IOHIDDeviceCreate(kCFAllocatorDefault
, service
));
240 VLOG(1) << "Unable to create IOHIDDevice object for " << service_path
245 std::vector
<uint8
> report_descriptor
;
246 if (!TryGetHidDataProperty(hid_device
, CFSTR(kIOHIDReportDescriptorKey
),
247 &report_descriptor
)) {
248 VLOG(1) << "Unable to get report descriptor for " << service_path
<< ".";
252 return new HidDeviceInfo(
253 service_path
, GetHidIntProperty(hid_device
, CFSTR(kIOHIDVendorIDKey
)),
254 GetHidIntProperty(hid_device
, CFSTR(kIOHIDProductIDKey
)),
255 GetHidStringProperty(hid_device
, CFSTR(kIOHIDProductKey
)),
256 GetHidStringProperty(hid_device
, CFSTR(kIOHIDSerialNumberKey
)),
257 kHIDBusTypeUSB
, // TODO(reillyg): Detect Bluetooth. crbug.com/443335
261 } // namespace device