fe4876141d7ff7b32554e80f0ff75b1e0809576b
[platform/framework/web/crosswalk.git] / src / device / hid / hid_service_mac.cc
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.
4
5 #include "device/hid/hid_service_mac.h"
6
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <IOKit/hid/IOHIDManager.h>
9
10 #include <set>
11 #include <string>
12 #include <vector>
13
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"
24
25 namespace device {
26
27 class HidServiceMac;
28
29 namespace {
30
31 typedef std::vector<IOHIDDeviceRef> HidDeviceList;
32
33 HidServiceMac* HidServiceFromContext(void* context) {
34   return static_cast<HidServiceMac*>(context);
35 }
36
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);
43 }
44
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));
50   if (devices)
51     CFSetApplyFunction(devices, HidEnumerationBackInserter, device_list);
52 }
53
54 bool TryGetHidIntProperty(IOHIDDeviceRef device,
55                           CFStringRef key,
56                           int32_t* result) {
57   CFNumberRef ref =
58       base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
59   return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
60 }
61
62 int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) {
63   int32_t value;
64   if (TryGetHidIntProperty(device, key, &value))
65     return value;
66   return 0;
67 }
68
69 bool TryGetHidStringProperty(IOHIDDeviceRef device,
70                              CFStringRef key,
71                              std::string* result) {
72   CFStringRef ref =
73       base::mac::CFCast<CFStringRef>(IOHIDDeviceGetProperty(device, key));
74   if (!ref) {
75     return false;
76   }
77   *result = base::SysCFStringRefToUTF8(ref);
78   return true;
79 }
80
81 std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) {
82   std::string value;
83   TryGetHidStringProperty(device, key, &value);
84   return value;
85 }
86
87 void GetReportIds(IOHIDElementRef element, std::set<int>* reportIDs) {
88   uint32_t reportID = IOHIDElementGetReportID(element);
89   if (reportID) {
90     reportIDs->insert(reportID);
91   }
92
93   CFArrayRef children = IOHIDElementGetChildren(element);
94   if (!children) {
95     return;
96   }
97
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);
103   }
104 }
105
106 void GetCollectionInfos(IOHIDDeviceRef device,
107                         bool* has_report_id,
108                         std::vector<HidCollectionInfo>* top_level_collections) {
109   STLClearObject(top_level_collections);
110   CFMutableDictionaryRef collections_filter =
111       CFDictionaryCreateMutable(kCFAllocatorDefault,
112                                 0,
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;
139       }
140       top_level_collections->push_back(collection_info);
141     }
142   }
143 }
144
145 }  // namespace
146
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));
152   if (!hid_manager_) {
153     LOG(ERROR) << "Failed to initialize HidManager";
154     return;
155   }
156   DCHECK(CFGetTypeID(hid_manager_) == IOHIDManagerGetTypeID());
157   IOHIDManagerOpen(hid_manager_, kIOHIDOptionsTypeNone);
158   IOHIDManagerSetDeviceMatching(hid_manager_, NULL);
159
160   // Enumerate all the currently known devices.
161   Enumerate();
162
163   // Register for plug/unplug notifications.
164   StartWatchingDevices();
165 }
166
167 HidServiceMac::~HidServiceMac() {
168   StopWatchingDevices();
169 }
170
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);
179 }
180
181 void HidServiceMac::StopWatchingDevices() {
182   DCHECK(thread_checker_.CalledOnValidThread());
183   if (!hid_manager_)
184     return;
185   IOHIDManagerUnscheduleFromRunLoop(
186       hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
187   IOHIDManagerClose(hid_manager_, kIOHIDOptionsTypeNone);
188 }
189
190 void HidServiceMac::AddDeviceCallback(void* context,
191                                       IOReturn result,
192                                       void* sender,
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)));
202 }
203
204 void HidServiceMac::RemoveDeviceCallback(void* context,
205                                          IOReturn result,
206                                          void* sender,
207                                          IOHIDDeviceRef hid_device) {
208   DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent());
209   HidServiceMac* service = HidServiceFromContext(context);
210   service->message_loop_->PostTask(
211       FROM_HERE,
212       base::Bind(&HidServiceMac::PlatformRemoveDevice,
213                  base::Unretained(service),
214                  base::Unretained(hid_device)));
215 }
216
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();
223        ++iter) {
224     IOHIDDeviceRef hid_device = *iter;
225     PlatformAddDevice(hid_device);
226   }
227 }
228
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--;
250   }
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--;
255   }
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--;
260   }
261   AddDevice(device_info);
262 }
263
264 void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device) {
265   DCHECK(thread_checker_.CalledOnValidThread());
266   RemoveDevice(hid_device);
267   CFRelease(hid_device);
268 }
269
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))
275     return NULL;
276   return scoped_refptr<HidConnection>(new HidConnectionMac(device_info));
277 }
278
279 }  // namespace device