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 <string> 11 #include <vector> 12 13 #include "base/bind.h" 14 #include "base/logging.h" 15 #include "base/message_loop/message_loop_proxy.h" 16 #include "base/stl_util.h" 17 #include "base/strings/string_number_conversions.h" 18 #include "base/threading/thread_restrictions.h" 19 #include "device/hid/hid_connection_mac.h" 20 #include "device/hid/hid_utils_mac.h" 21 22 namespace device { 23 24 class HidServiceMac; 25 26 namespace { 27 28 typedef std::vector<IOHIDDeviceRef> HidDeviceList; 29 30 HidServiceMac* HidServiceFromContext(void* context) { 31 return static_cast<HidServiceMac*>(context); 32 } 33 34 // Callback for CFSetApplyFunction as used by EnumerateHidDevices. 35 void HidEnumerationBackInserter(const void* value, void* context) { 36 HidDeviceList* devices = static_cast<HidDeviceList*>(context); 37 const IOHIDDeviceRef device = 38 static_cast<IOHIDDeviceRef>(const_cast<void*>(value)); 39 devices->push_back(device); 40 } 41 42 void EnumerateHidDevices(IOHIDManagerRef hid_manager, 43 HidDeviceList* device_list) { 44 DCHECK(device_list->size() == 0); 45 // Note that our ownership of each copied device is implied. 46 base::ScopedCFTypeRef<CFSetRef> devices(IOHIDManagerCopyDevices(hid_manager)); 47 if (devices) 48 CFSetApplyFunction(devices, HidEnumerationBackInserter, device_list); 49 } 50 51 } // namespace 52 53 HidServiceMac::HidServiceMac() { 54 DCHECK(thread_checker_.CalledOnValidThread()); 55 message_loop_ = base::MessageLoopProxy::current(); 56 DCHECK(message_loop_); 57 hid_manager_.reset(IOHIDManagerCreate(NULL, 0)); 58 if (!hid_manager_) { 59 LOG(ERROR) << "Failed to initialize HidManager"; 60 return; 61 } 62 DCHECK(CFGetTypeID(hid_manager_) == IOHIDManagerGetTypeID()); 63 IOHIDManagerOpen(hid_manager_, kIOHIDOptionsTypeNone); 64 IOHIDManagerSetDeviceMatching(hid_manager_, NULL); 65 66 // Enumerate all the currently known devices. 67 Enumerate(); 68 69 // Register for plug/unplug notifications. 70 StartWatchingDevices(); 71 } 72 73 HidServiceMac::~HidServiceMac() { 74 StopWatchingDevices(); 75 } 76 77 void HidServiceMac::StartWatchingDevices() { 78 DCHECK(thread_checker_.CalledOnValidThread()); 79 IOHIDManagerRegisterDeviceMatchingCallback( 80 hid_manager_, &AddDeviceCallback, this); 81 IOHIDManagerRegisterDeviceRemovalCallback( 82 hid_manager_, &RemoveDeviceCallback, this); 83 IOHIDManagerScheduleWithRunLoop( 84 hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); 85 } 86 87 void HidServiceMac::StopWatchingDevices() { 88 DCHECK(thread_checker_.CalledOnValidThread()); 89 if (!hid_manager_) 90 return; 91 IOHIDManagerUnscheduleFromRunLoop( 92 hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); 93 IOHIDManagerClose(hid_manager_, kIOHIDOptionsTypeNone); 94 } 95 96 void HidServiceMac::AddDeviceCallback(void* context, 97 IOReturn result, 98 void* sender, 99 IOHIDDeviceRef hid_device) { 100 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); 101 // Claim ownership of the device. 102 CFRetain(hid_device); 103 HidServiceMac* service = HidServiceFromContext(context); 104 service->message_loop_->PostTask(FROM_HERE, 105 base::Bind(&HidServiceMac::PlatformAddDevice, 106 base::Unretained(service), 107 base::Unretained(hid_device))); 108 } 109 110 void HidServiceMac::RemoveDeviceCallback(void* context, 111 IOReturn result, 112 void* sender, 113 IOHIDDeviceRef hid_device) { 114 DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); 115 HidServiceMac* service = HidServiceFromContext(context); 116 service->message_loop_->PostTask( 117 FROM_HERE, 118 base::Bind(&HidServiceMac::PlatformRemoveDevice, 119 base::Unretained(service), 120 base::Unretained(hid_device))); 121 } 122 123 void HidServiceMac::Enumerate() { 124 DCHECK(thread_checker_.CalledOnValidThread()); 125 HidDeviceList devices; 126 EnumerateHidDevices(hid_manager_, &devices); 127 for (HidDeviceList::const_iterator iter = devices.begin(); 128 iter != devices.end(); 129 ++iter) { 130 IOHIDDeviceRef hid_device = *iter; 131 PlatformAddDevice(hid_device); 132 } 133 } 134 135 void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef hid_device) { 136 // Note that our ownership of hid_device is implied if calling this method. 137 // It is balanced in PlatformRemoveDevice. 138 DCHECK(thread_checker_.CalledOnValidThread()); 139 140 HidDeviceInfo device_info; 141 device_info.device_id = hid_device; 142 device_info.vendor_id = 143 GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey)); 144 device_info.product_id = 145 GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey)); 146 device_info.input_report_size = 147 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey)); 148 device_info.output_report_size = 149 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey)); 150 device_info.feature_report_size = 151 GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey)); 152 CFTypeRef deviceUsagePairsRaw = 153 IOHIDDeviceGetProperty(hid_device, CFSTR(kIOHIDDeviceUsagePairsKey)); 154 CFArrayRef deviceUsagePairs = 155 base::mac::CFCast<CFArrayRef>(deviceUsagePairsRaw); 156 CFIndex deviceUsagePairsCount = CFArrayGetCount(deviceUsagePairs); 157 for (CFIndex i = 0; i < deviceUsagePairsCount; i++) { 158 CFDictionaryRef deviceUsagePair = base::mac::CFCast<CFDictionaryRef>( 159 CFArrayGetValueAtIndex(deviceUsagePairs, i)); 160 CFNumberRef usage_raw = base::mac::CFCast<CFNumberRef>( 161 CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsageKey))); 162 uint16_t usage; 163 CFNumberGetValue(usage_raw, kCFNumberSInt32Type, &usage); 164 CFNumberRef page_raw = base::mac::CFCast<CFNumberRef>( 165 CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsagePageKey))); 166 HidUsageAndPage::Page page; 167 CFNumberGetValue(page_raw, kCFNumberSInt32Type, &page); 168 device_info.usages.push_back(HidUsageAndPage(usage, page)); 169 } 170 device_info.product_name = 171 GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey)); 172 device_info.serial_number = 173 GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey)); 174 AddDevice(device_info); 175 } 176 177 void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device) { 178 DCHECK(thread_checker_.CalledOnValidThread()); 179 RemoveDevice(hid_device); 180 CFRelease(hid_device); 181 } 182 183 scoped_refptr<HidConnection> HidServiceMac::Connect( 184 const HidDeviceId& device_id) { 185 DCHECK(thread_checker_.CalledOnValidThread()); 186 HidDeviceInfo device_info; 187 if (!GetDeviceInfo(device_id, &device_info)) 188 return NULL; 189 return scoped_refptr<HidConnection>(new HidConnectionMac(device_info)); 190 } 191 192 } // namespace device 193