Upstream version 11.39.250.0
[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/IOHIDDevice.h>
9
10 #include <set>
11 #include <string>
12 #include <vector>
13
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"
25
26 namespace device {
27
28 namespace {
29
30 bool TryGetHidIntProperty(IOHIDDeviceRef device,
31                           CFStringRef key,
32                           int32_t* result) {
33   CFNumberRef ref =
34       base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key));
35   return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result);
36 }
37
38 int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) {
39   int32_t value;
40   if (TryGetHidIntProperty(device, key, &value))
41     return value;
42   return 0;
43 }
44
45 bool TryGetHidStringProperty(IOHIDDeviceRef device,
46                              CFStringRef key,
47                              std::string* result) {
48   CFStringRef ref =
49       base::mac::CFCast<CFStringRef>(IOHIDDeviceGetProperty(device, key));
50   if (!ref) {
51     return false;
52   }
53   *result = base::SysCFStringRefToUTF8(ref);
54   return true;
55 }
56
57 std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) {
58   std::string value;
59   TryGetHidStringProperty(device, key, &value);
60   return value;
61 }
62
63 void GetReportIds(IOHIDElementRef element, std::set<int>* reportIDs) {
64   uint32_t reportID = IOHIDElementGetReportID(element);
65   if (reportID) {
66     reportIDs->insert(reportID);
67   }
68
69   CFArrayRef children = IOHIDElementGetChildren(element);
70   if (!children) {
71     return;
72   }
73
74   CFIndex childrenCount = CFArrayGetCount(children);
75   for (CFIndex j = 0; j < childrenCount; ++j) {
76     const IOHIDElementRef child = static_cast<IOHIDElementRef>(
77         const_cast<void*>(CFArrayGetValueAtIndex(children, j)));
78     GetReportIds(child, reportIDs);
79   }
80 }
81
82 void GetCollectionInfos(IOHIDDeviceRef device,
83                         bool* has_report_id,
84                         std::vector<HidCollectionInfo>* top_level_collections) {
85   STLClearObject(top_level_collections);
86   CFMutableDictionaryRef collections_filter =
87       CFDictionaryCreateMutable(kCFAllocatorDefault,
88                                 0,
89                                 &kCFTypeDictionaryKeyCallBacks,
90                                 &kCFTypeDictionaryValueCallBacks);
91   const int kCollectionTypeValue = kIOHIDElementTypeCollection;
92   CFNumberRef collection_type_id = CFNumberCreate(
93       kCFAllocatorDefault, kCFNumberIntType, &kCollectionTypeValue);
94   CFDictionarySetValue(
95       collections_filter, CFSTR(kIOHIDElementTypeKey), collection_type_id);
96   CFRelease(collection_type_id);
97   CFArrayRef collections = IOHIDDeviceCopyMatchingElements(
98       device, collections_filter, kIOHIDOptionsTypeNone);
99   CFIndex collectionsCount = CFArrayGetCount(collections);
100   *has_report_id = false;
101   for (CFIndex i = 0; i < collectionsCount; i++) {
102     const IOHIDElementRef collection = static_cast<IOHIDElementRef>(
103         const_cast<void*>(CFArrayGetValueAtIndex(collections, i)));
104     // Top-Level Collection has no parent
105     if (IOHIDElementGetParent(collection) == 0) {
106       HidCollectionInfo collection_info;
107       HidUsageAndPage::Page page = static_cast<HidUsageAndPage::Page>(
108           IOHIDElementGetUsagePage(collection));
109       uint16_t usage = IOHIDElementGetUsage(collection);
110       collection_info.usage = HidUsageAndPage(usage, page);
111       // Explore children recursively and retrieve their report IDs
112       GetReportIds(collection, &collection_info.report_ids);
113       if (collection_info.report_ids.size() > 0) {
114         *has_report_id = true;
115       }
116       top_level_collections->push_back(collection_info);
117     }
118   }
119 }
120
121 void PopulateDeviceInfo(io_service_t service, HidDeviceInfo* device_info) {
122   io_string_t service_path;
123   IOReturn result =
124       IORegistryEntryGetPath(service, kIOServicePlane, service_path);
125   if (result != kIOReturnSuccess) {
126     VLOG(1) << "Failed to get IOService path: "
127             << base::StringPrintf("0x%04x", result);
128     return;
129   }
130
131   base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
132       IOHIDDeviceCreate(kCFAllocatorDefault, service));
133   device_info->device_id = service_path;
134   device_info->vendor_id =
135       GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey));
136   device_info->product_id =
137       GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey));
138   device_info->product_name =
139       GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey));
140   device_info->serial_number =
141       GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey));
142   GetCollectionInfos(
143       hid_device, &device_info->has_report_id, &device_info->collections);
144   device_info->max_input_report_size =
145       GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey));
146   if (device_info->has_report_id && device_info->max_input_report_size > 0) {
147     device_info->max_input_report_size--;
148   }
149   device_info->max_output_report_size =
150       GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey));
151   if (device_info->has_report_id && device_info->max_output_report_size > 0) {
152     device_info->max_output_report_size--;
153   }
154   device_info->max_feature_report_size =
155       GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey));
156   if (device_info->has_report_id && device_info->max_feature_report_size > 0) {
157     device_info->max_feature_report_size--;
158   }
159 }
160
161 }  // namespace
162
163 HidServiceMac::HidServiceMac(
164     scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
165     : file_task_runner_(file_task_runner) {
166   task_runner_ = base::ThreadTaskRunnerHandle::Get();
167   DCHECK(task_runner_.get());
168
169   notify_port_ = IONotificationPortCreate(kIOMasterPortDefault);
170   CFRunLoopAddSource(CFRunLoopGetMain(),
171                      IONotificationPortGetRunLoopSource(notify_port_),
172                      kCFRunLoopDefaultMode);
173
174   io_iterator_t iterator;
175   IOReturn result =
176       IOServiceAddMatchingNotification(notify_port_,
177                                        kIOFirstMatchNotification,
178                                        IOServiceMatching(kIOHIDDeviceKey),
179                                        FirstMatchCallback,
180                                        this,
181                                        &iterator);
182   if (result != kIOReturnSuccess) {
183     LOG(ERROR) << "Failed to listen for device arrival: "
184                << base::StringPrintf("0x%04x", result);
185     return;
186   }
187
188   // Drain the iterator to arm the notification.
189   devices_added_iterator_.reset(iterator);
190   AddDevices();
191   iterator = IO_OBJECT_NULL;
192
193   result = IOServiceAddMatchingNotification(notify_port_,
194                                             kIOTerminatedNotification,
195                                             IOServiceMatching(kIOHIDDeviceKey),
196                                             TerminatedCallback,
197                                             this,
198                                             &iterator);
199   if (result != kIOReturnSuccess) {
200     LOG(ERROR) << "Failed to listen for device removal: "
201                << base::StringPrintf("0x%04x", result);
202     return;
203   }
204
205   // Drain devices_added_iterator_ to arm the notification.
206   devices_removed_iterator_.reset(iterator);
207   RemoveDevices();
208 }
209
210 HidServiceMac::~HidServiceMac() {
211 }
212
213 // static
214 void HidServiceMac::FirstMatchCallback(void* context, io_iterator_t iterator) {
215   DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
216   HidServiceMac* service = static_cast<HidServiceMac*>(context);
217   DCHECK_EQ(service->devices_added_iterator_, iterator);
218   service->AddDevices();
219 }
220
221 // static
222 void HidServiceMac::TerminatedCallback(void* context, io_iterator_t iterator) {
223   DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
224   HidServiceMac* service = static_cast<HidServiceMac*>(context);
225   DCHECK_EQ(service->devices_removed_iterator_, iterator);
226   service->RemoveDevices();
227 }
228
229 void HidServiceMac::AddDevices() {
230   DCHECK(thread_checker_.CalledOnValidThread());
231
232   io_service_t device;
233   while ((device = IOIteratorNext(devices_added_iterator_)) != IO_OBJECT_NULL) {
234     HidDeviceInfo device_info;
235     PopulateDeviceInfo(device, &device_info);
236     AddDevice(device_info);
237     // The reference retained by IOIteratorNext is released below in
238     // RemoveDevices when the device is removed.
239   }
240 }
241
242 void HidServiceMac::RemoveDevices() {
243   DCHECK(thread_checker_.CalledOnValidThread());
244
245   io_service_t device;
246   while ((device = IOIteratorNext(devices_removed_iterator_)) !=
247          IO_OBJECT_NULL) {
248     io_string_t service_path;
249     IOReturn result =
250         IORegistryEntryGetPath(device, kIOServicePlane, service_path);
251     if (result == kIOReturnSuccess) {
252       RemoveDevice(service_path);
253     }
254
255     // Release reference retained by AddDevices above.
256     IOObjectRelease(device);
257     // Release the reference retained by IOIteratorNext.
258     IOObjectRelease(device);
259   }
260 }
261
262 scoped_refptr<HidConnection> HidServiceMac::Connect(
263     const HidDeviceId& device_id) {
264   DCHECK(thread_checker_.CalledOnValidThread());
265
266   const auto& map_entry = devices().find(device_id);
267   if (map_entry == devices().end()) {
268     return NULL;
269   }
270   const HidDeviceInfo& device_info = map_entry->second;
271
272   io_string_t service_path;
273   strncpy(service_path, device_id.c_str(), sizeof service_path);
274   base::mac::ScopedIOObject<io_service_t> service(
275       IORegistryEntryFromPath(kIOMasterPortDefault, service_path));
276   if (!service.get()) {
277     VLOG(1) << "IOService not found for path: " << device_id;
278     return NULL;
279   }
280
281   base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
282       IOHIDDeviceCreate(kCFAllocatorDefault, service));
283   IOReturn result = IOHIDDeviceOpen(hid_device, kIOHIDOptionsTypeNone);
284   if (result != kIOReturnSuccess) {
285     VLOG(1) << "Failed to open device: "
286             << base::StringPrintf("0x%04x", result);
287     return NULL;
288   }
289
290   return make_scoped_refptr(new HidConnectionMac(
291       hid_device.release(), device_info, file_task_runner_));
292 }
293
294 }  // namespace device