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.h"
18 #include "base/message_loop/message_loop_proxy.h"
19 #include "base/stl_util.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/sys_string_conversions.h"
22 #include "base/threading/thread_restrictions.h"
23 #include "device/hid/hid_connection_mac.h"
31 typedef std::vector
<IOHIDDeviceRef
> HidDeviceList
;
33 HidServiceMac
* HidServiceFromContext(void* context
) {
34 return static_cast<HidServiceMac
*>(context
);
37 // Callback for CFSetApplyFunction as used by EnumerateHidDevices.
38 void HidEnumerationBackInserter(const void* value
, void* context
) {
39 HidDeviceList
* devices
= static_cast<HidDeviceList
*>(context
);
40 const IOHIDDeviceRef device
=
41 static_cast<IOHIDDeviceRef
>(const_cast<void*>(value
));
42 devices
->push_back(device
);
45 void EnumerateHidDevices(IOHIDManagerRef hid_manager
,
46 HidDeviceList
* device_list
) {
47 DCHECK(device_list
->size() == 0);
48 // Note that our ownership of each copied device is implied.
49 base::ScopedCFTypeRef
<CFSetRef
> devices(IOHIDManagerCopyDevices(hid_manager
));
51 CFSetApplyFunction(devices
, HidEnumerationBackInserter
, device_list
);
54 bool TryGetHidIntProperty(IOHIDDeviceRef device
,
58 base::mac::CFCast
<CFNumberRef
>(IOHIDDeviceGetProperty(device
, key
));
59 return ref
&& CFNumberGetValue(ref
, kCFNumberSInt32Type
, result
);
62 int32_t GetHidIntProperty(IOHIDDeviceRef device
, CFStringRef key
) {
64 if (TryGetHidIntProperty(device
, key
, &value
))
69 bool TryGetHidStringProperty(IOHIDDeviceRef device
,
71 std::string
* result
) {
73 base::mac::CFCast
<CFStringRef
>(IOHIDDeviceGetProperty(device
, key
));
77 *result
= base::SysCFStringRefToUTF8(ref
);
81 std::string
GetHidStringProperty(IOHIDDeviceRef device
, CFStringRef key
) {
83 TryGetHidStringProperty(device
, key
, &value
);
87 void GetReportIds(IOHIDElementRef element
, std::set
<int>* reportIDs
) {
88 uint32_t reportID
= IOHIDElementGetReportID(element
);
90 reportIDs
->insert(reportID
);
93 CFArrayRef children
= IOHIDElementGetChildren(element
);
98 CFIndex childrenCount
= CFArrayGetCount(children
);
99 for (CFIndex j
= 0; j
< childrenCount
; ++j
) {
100 const IOHIDElementRef child
= static_cast<IOHIDElementRef
>(
101 const_cast<void*>(CFArrayGetValueAtIndex(children
, j
)));
102 GetReportIds(child
, reportIDs
);
106 void GetCollectionInfos(IOHIDDeviceRef device
,
108 std::vector
<HidCollectionInfo
>* top_level_collections
) {
109 STLClearObject(top_level_collections
);
110 CFMutableDictionaryRef collections_filter
=
111 CFDictionaryCreateMutable(kCFAllocatorDefault
,
113 &kCFTypeDictionaryKeyCallBacks
,
114 &kCFTypeDictionaryValueCallBacks
);
115 const int kCollectionTypeValue
= kIOHIDElementTypeCollection
;
116 CFNumberRef collection_type_id
= CFNumberCreate(
117 kCFAllocatorDefault
, kCFNumberIntType
, &kCollectionTypeValue
);
118 CFDictionarySetValue(
119 collections_filter
, CFSTR(kIOHIDElementTypeKey
), collection_type_id
);
120 CFRelease(collection_type_id
);
121 CFArrayRef collections
= IOHIDDeviceCopyMatchingElements(
122 device
, collections_filter
, kIOHIDOptionsTypeNone
);
123 CFIndex collectionsCount
= CFArrayGetCount(collections
);
124 *has_report_id
= false;
125 for (CFIndex i
= 0; i
< collectionsCount
; i
++) {
126 const IOHIDElementRef collection
= static_cast<IOHIDElementRef
>(
127 const_cast<void*>(CFArrayGetValueAtIndex(collections
, i
)));
128 // Top-Level Collection has no parent
129 if (IOHIDElementGetParent(collection
) == 0) {
130 HidCollectionInfo collection_info
;
131 HidUsageAndPage::Page page
= static_cast<HidUsageAndPage::Page
>(
132 IOHIDElementGetUsagePage(collection
));
133 uint16_t usage
= IOHIDElementGetUsage(collection
);
134 collection_info
.usage
= HidUsageAndPage(usage
, page
);
135 // Explore children recursively and retrieve their report IDs
136 GetReportIds(collection
, &collection_info
.report_ids
);
137 if (collection_info
.report_ids
.size() > 0) {
138 *has_report_id
= true;
140 top_level_collections
->push_back(collection_info
);
147 HidServiceMac::HidServiceMac() {
148 DCHECK(thread_checker_
.CalledOnValidThread());
149 message_loop_
= base::MessageLoopProxy::current();
150 DCHECK(message_loop_
.get());
151 hid_manager_
.reset(IOHIDManagerCreate(NULL
, 0));
153 LOG(ERROR
) << "Failed to initialize HidManager";
156 DCHECK(CFGetTypeID(hid_manager_
) == IOHIDManagerGetTypeID());
157 IOHIDManagerOpen(hid_manager_
, kIOHIDOptionsTypeNone
);
158 IOHIDManagerSetDeviceMatching(hid_manager_
, NULL
);
160 // Enumerate all the currently known devices.
163 // Register for plug/unplug notifications.
164 StartWatchingDevices();
167 HidServiceMac::~HidServiceMac() {
168 StopWatchingDevices();
171 void HidServiceMac::StartWatchingDevices() {
172 DCHECK(thread_checker_
.CalledOnValidThread());
173 IOHIDManagerRegisterDeviceMatchingCallback(
174 hid_manager_
, &AddDeviceCallback
, this);
175 IOHIDManagerRegisterDeviceRemovalCallback(
176 hid_manager_
, &RemoveDeviceCallback
, this);
177 IOHIDManagerScheduleWithRunLoop(
178 hid_manager_
, CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
181 void HidServiceMac::StopWatchingDevices() {
182 DCHECK(thread_checker_
.CalledOnValidThread());
185 IOHIDManagerUnscheduleFromRunLoop(
186 hid_manager_
, CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
187 IOHIDManagerClose(hid_manager_
, kIOHIDOptionsTypeNone
);
190 void HidServiceMac::AddDeviceCallback(void* context
,
193 IOHIDDeviceRef hid_device
) {
194 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent());
195 // Claim ownership of the device.
196 CFRetain(hid_device
);
197 HidServiceMac
* service
= HidServiceFromContext(context
);
198 service
->message_loop_
->PostTask(FROM_HERE
,
199 base::Bind(&HidServiceMac::PlatformAddDevice
,
200 base::Unretained(service
),
201 base::Unretained(hid_device
)));
204 void HidServiceMac::RemoveDeviceCallback(void* context
,
207 IOHIDDeviceRef hid_device
) {
208 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent());
209 HidServiceMac
* service
= HidServiceFromContext(context
);
210 service
->message_loop_
->PostTask(
212 base::Bind(&HidServiceMac::PlatformRemoveDevice
,
213 base::Unretained(service
),
214 base::Unretained(hid_device
)));
217 void HidServiceMac::Enumerate() {
218 DCHECK(thread_checker_
.CalledOnValidThread());
219 HidDeviceList devices
;
220 EnumerateHidDevices(hid_manager_
, &devices
);
221 for (HidDeviceList::const_iterator iter
= devices
.begin();
222 iter
!= devices
.end();
224 IOHIDDeviceRef hid_device
= *iter
;
225 PlatformAddDevice(hid_device
);
229 void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef hid_device
) {
230 // Note that our ownership of hid_device is implied if calling this method.
231 // It is balanced in PlatformRemoveDevice.
232 DCHECK(thread_checker_
.CalledOnValidThread());
233 HidDeviceInfo device_info
;
234 device_info
.device_id
= hid_device
;
235 device_info
.vendor_id
=
236 GetHidIntProperty(hid_device
, CFSTR(kIOHIDVendorIDKey
));
237 device_info
.product_id
=
238 GetHidIntProperty(hid_device
, CFSTR(kIOHIDProductIDKey
));
239 device_info
.product_name
=
240 GetHidStringProperty(hid_device
, CFSTR(kIOHIDProductKey
));
241 device_info
.serial_number
=
242 GetHidStringProperty(hid_device
, CFSTR(kIOHIDSerialNumberKey
));
243 GetCollectionInfos(hid_device
,
244 &device_info
.has_report_id
,
245 &device_info
.collections
);
246 device_info
.max_input_report_size
=
247 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxInputReportSizeKey
));
248 if (device_info
.has_report_id
&& device_info
.max_input_report_size
> 0) {
249 device_info
.max_input_report_size
--;
251 device_info
.max_output_report_size
=
252 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxOutputReportSizeKey
));
253 if (device_info
.has_report_id
&& device_info
.max_output_report_size
> 0) {
254 device_info
.max_output_report_size
--;
256 device_info
.max_feature_report_size
=
257 GetHidIntProperty(hid_device
, CFSTR(kIOHIDMaxFeatureReportSizeKey
));
258 if (device_info
.has_report_id
&& device_info
.max_feature_report_size
> 0) {
259 device_info
.max_feature_report_size
--;
261 AddDevice(device_info
);
264 void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device
) {
265 DCHECK(thread_checker_
.CalledOnValidThread());
266 RemoveDevice(hid_device
);
267 CFRelease(hid_device
);
270 scoped_refptr
<HidConnection
> HidServiceMac::Connect(
271 const HidDeviceId
& device_id
) {
272 DCHECK(thread_checker_
.CalledOnValidThread());
273 HidDeviceInfo device_info
;
274 if (!GetDeviceInfo(device_id
, &device_info
))
276 return scoped_refptr
<HidConnection
>(new HidConnectionMac(device_info
));
279 } // namespace device