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/IOHIDManager.h>
14 #include "base/bind.h"
15 #include "base/logging.h"
16 #include "base/mac/foundation_util.h"
17 #include "base/message_loop/message_loop_proxy.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/sys_string_conversions.h"
21 #include "base/threading/thread_restrictions.h"
22 #include "device/hid/hid_connection_mac.h"
30 typedef std::vector
<IOHIDDeviceRef
> HidDeviceList
;
32 HidServiceMac
* HidServiceFromContext(void* context
) {
33 return static_cast<HidServiceMac
*>(context
);
36 // Callback for CFSetApplyFunction as used by EnumerateHidDevices.
37 void HidEnumerationBackInserter(const void* value
, void* context
) {
38 HidDeviceList
* devices
= static_cast<HidDeviceList
*>(context
);
39 const IOHIDDeviceRef device
=
40 static_cast<IOHIDDeviceRef
>(const_cast<void*>(value
));
41 devices
->push_back(device
);
44 void EnumerateHidDevices(IOHIDManagerRef hid_manager
,
45 HidDeviceList
* device_list
) {
46 DCHECK(device_list
->size() == 0);
47 // Note that our ownership of each copied device is implied.
48 base::ScopedCFTypeRef
<CFSetRef
> devices(IOHIDManagerCopyDevices(hid_manager
));
50 CFSetApplyFunction(devices
, HidEnumerationBackInserter
, device_list
);
53 bool TryGetHidIntProperty(IOHIDDeviceRef device
,
57 base::mac::CFCast
<CFNumberRef
>(IOHIDDeviceGetProperty(device
, key
));
58 return ref
&& CFNumberGetValue(ref
, kCFNumberSInt32Type
, result
);
61 int32_t GetHidIntProperty(IOHIDDeviceRef device
, CFStringRef key
) {
63 if (TryGetHidIntProperty(device
, key
, &value
))
68 bool TryGetHidStringProperty(IOHIDDeviceRef device
,
70 std::string
* result
) {
72 base::mac::CFCast
<CFStringRef
>(IOHIDDeviceGetProperty(device
, key
));
76 *result
= base::SysCFStringRefToUTF8(ref
);
80 std::string
GetHidStringProperty(IOHIDDeviceRef device
, CFStringRef key
) {
82 TryGetHidStringProperty(device
, key
, &value
);
86 void GetReportIds(IOHIDElementRef element
, std::set
<int>& reportIDs
) {
87 CFArrayRef children
= IOHIDElementGetChildren(element
);
90 CFIndex childrenCount
= CFArrayGetCount(children
);
91 for (CFIndex j
= 0; j
< childrenCount
; ++j
) {
92 const IOHIDElementRef child
= static_cast<IOHIDElementRef
>(
93 const_cast<void*>(CFArrayGetValueAtIndex(children
, j
)));
94 uint32_t reportID
= IOHIDElementGetReportID(child
);
96 reportIDs
.insert(reportID
);
98 GetReportIds(child
, reportIDs
);
102 void GetCollectionInfos(IOHIDDeviceRef device
,
103 std::vector
<HidCollectionInfo
>* top_level_collections
) {
104 STLClearObject(top_level_collections
);
105 CFMutableDictionaryRef collections_filter
=
106 CFDictionaryCreateMutable(kCFAllocatorDefault
,
108 &kCFTypeDictionaryKeyCallBacks
,
109 &kCFTypeDictionaryValueCallBacks
);
110 const int kCollectionTypeValue
= kIOHIDElementTypeCollection
;
111 CFNumberRef collection_type_id
= CFNumberCreate(
112 kCFAllocatorDefault
, kCFNumberIntType
, &kCollectionTypeValue
);
113 CFDictionarySetValue(
114 collections_filter
, CFSTR(kIOHIDElementTypeKey
), collection_type_id
);
115 CFRelease(collection_type_id
);
116 CFArrayRef collections
= IOHIDDeviceCopyMatchingElements(
117 device
, collections_filter
, kIOHIDOptionsTypeNone
);
118 CFIndex collectionsCount
= CFArrayGetCount(collections
);
119 for (CFIndex i
= 0; i
< collectionsCount
; i
++) {
120 const IOHIDElementRef collection
= static_cast<IOHIDElementRef
>(
121 const_cast<void*>(CFArrayGetValueAtIndex(collections
, i
)));
122 // Top-Level Collection has no parent
123 if (IOHIDElementGetParent(collection
) == 0) {
124 HidCollectionInfo collection_info
;
125 HidUsageAndPage::Page page
= static_cast<HidUsageAndPage::Page
>(
126 IOHIDElementGetUsagePage(collection
));
127 uint16_t usage
= IOHIDElementGetUsage(collection
);
128 collection_info
.usage
= HidUsageAndPage(usage
, page
);
129 // Explore children recursively and retrieve their report IDs
130 GetReportIds(collection
, collection_info
.report_ids
);
131 top_level_collections
->push_back(collection_info
);
138 HidServiceMac::HidServiceMac() {
139 DCHECK(thread_checker_
.CalledOnValidThread());
140 message_loop_
= base::MessageLoopProxy::current();
141 DCHECK(message_loop_
);
142 hid_manager_
.reset(IOHIDManagerCreate(NULL
, 0));
144 LOG(ERROR
) << "Failed to initialize HidManager";
147 DCHECK(CFGetTypeID(hid_manager_
) == IOHIDManagerGetTypeID());
148 IOHIDManagerOpen(hid_manager_
, kIOHIDOptionsTypeNone
);
149 IOHIDManagerSetDeviceMatching(hid_manager_
, NULL
);
151 // Enumerate all the currently known devices.
154 // Register for plug/unplug notifications.
155 StartWatchingDevices();
158 HidServiceMac::~HidServiceMac() {
159 StopWatchingDevices();
162 void HidServiceMac::StartWatchingDevices() {
163 DCHECK(thread_checker_
.CalledOnValidThread());
164 IOHIDManagerRegisterDeviceMatchingCallback(
165 hid_manager_
, &AddDeviceCallback
, this);
166 IOHIDManagerRegisterDeviceRemovalCallback(
167 hid_manager_
, &RemoveDeviceCallback
, this);
168 IOHIDManagerScheduleWithRunLoop(
169 hid_manager_
, CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
172 void HidServiceMac::StopWatchingDevices() {
173 DCHECK(thread_checker_
.CalledOnValidThread());
176 IOHIDManagerUnscheduleFromRunLoop(
177 hid_manager_
, CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
178 IOHIDManagerClose(hid_manager_
, kIOHIDOptionsTypeNone
);
181 void HidServiceMac::AddDeviceCallback(void* context
,
184 IOHIDDeviceRef hid_device
) {
185 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent());
186 // Claim ownership of the device.
187 CFRetain(hid_device
);
188 HidServiceMac
* service
= HidServiceFromContext(context
);
189 service
->message_loop_
->PostTask(FROM_HERE
,
190 base::Bind(&HidServiceMac::PlatformAddDevice
,
191 base::Unretained(service
),
192 base::Unretained(hid_device
)));
195 void HidServiceMac::RemoveDeviceCallback(void* context
,
198 IOHIDDeviceRef hid_device
) {
199 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent());
200 HidServiceMac
* service
= HidServiceFromContext(context
);
201 service
->message_loop_
->PostTask(
203 base::Bind(&HidServiceMac::PlatformRemoveDevice
,
204 base::Unretained(service
),
205 base::Unretained(hid_device
)));
208 void HidServiceMac::Enumerate() {
209 DCHECK(thread_checker_
.CalledOnValidThread());
210 HidDeviceList devices
;
211 EnumerateHidDevices(hid_manager_
, &devices
);
212 for (HidDeviceList::const_iterator iter
= devices
.begin();
213 iter
!= devices
.end();
215 IOHIDDeviceRef hid_device
= *iter
;
216 PlatformAddDevice(hid_device
);
220 void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef hid_device
) {
221 // Note that our ownership of hid_device is implied if calling this method.
222 // It is balanced in PlatformRemoveDevice.
223 DCHECK(thread_checker_
.CalledOnValidThread());
224 HidDeviceInfo device_info
;
225 device_info
.device_id
= hid_device
;
226 device_info
.vendor_id
=
227 GetHidIntProperty(hid_device
, CFSTR(kIOHIDVendorIDKey
));
228 device_info
.product_id
=
229 GetHidIntProperty(hid_device
, CFSTR(kIOHIDProductIDKey
));
230 device_info
.product_name
=
231 GetHidStringProperty(hid_device
, CFSTR(kIOHIDProductKey
));
232 device_info
.serial_number
=
233 GetHidStringProperty(hid_device
, CFSTR(kIOHIDSerialNumberKey
));
234 GetCollectionInfos(hid_device
, &device_info
.collections
);
235 device_info
.max_input_report_size
=
236 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxInputReportSizeKey
));
237 device_info
.max_output_report_size
=
238 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxOutputReportSizeKey
));
239 device_info
.max_feature_report_size
=
240 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxFeatureReportSizeKey
));
241 AddDevice(device_info
);
244 void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device
) {
245 DCHECK(thread_checker_
.CalledOnValidThread());
246 RemoveDevice(hid_device
);
247 CFRelease(hid_device
);
250 scoped_refptr
<HidConnection
> HidServiceMac::Connect(
251 const HidDeviceId
& device_id
) {
252 DCHECK(thread_checker_
.CalledOnValidThread());
253 HidDeviceInfo device_info
;
254 if (!GetDeviceInfo(device_id
, &device_info
))
256 return scoped_refptr
<HidConnection
>(new HidConnectionMac(device_info
));
259 } // namespace device