// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/hid/hid_service_mac.h" #include <CoreFoundation/CoreFoundation.h> #include <IOKit/hid/IOHIDManager.h> #include <set> #include <string> #include <vector> #include "base/bind.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/thread_restrictions.h" #include "device/hid/hid_connection_mac.h" namespace device { class HidServiceMac; namespace { typedef std::vector<IOHIDDeviceRef> HidDeviceList; HidServiceMac* HidServiceFromContext(void* context) { return static_cast<HidServiceMac*>(context); } // Callback for CFSetApplyFunction as used by EnumerateHidDevices. void HidEnumerationBackInserter(const void* value, void* context) { HidDeviceList* devices = static_cast<HidDeviceList*>(context); const IOHIDDeviceRef device = static_cast<IOHIDDeviceRef>(const_cast<void*>(value)); devices->push_back(device); } void EnumerateHidDevices(IOHIDManagerRef hid_manager, HidDeviceList* device_list) { DCHECK(device_list->size() == 0); // Note that our ownership of each copied device is implied. base::ScopedCFTypeRef<CFSetRef> devices(IOHIDManagerCopyDevices(hid_manager)); if (devices) CFSetApplyFunction(devices, HidEnumerationBackInserter, device_list); } bool TryGetHidIntProperty(IOHIDDeviceRef device, CFStringRef key, int32_t* result) { CFNumberRef ref = base::mac::CFCast<CFNumberRef>(IOHIDDeviceGetProperty(device, key)); return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result); } int32_t GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key) { int32_t value; if (TryGetHidIntProperty(device, key, &value)) return value; return 0; } bool TryGetHidStringProperty(IOHIDDeviceRef device, CFStringRef key, std::string* result) { CFStringRef ref = base::mac::CFCast<CFStringRef>(IOHIDDeviceGetProperty(device, key)); if (!ref) { return false; } *result = base::SysCFStringRefToUTF8(ref); return true; } std::string GetHidStringProperty(IOHIDDeviceRef device, CFStringRef key) { std::string value; TryGetHidStringProperty(device, key, &value); return value; } void GetReportIds(IOHIDElementRef element, std::set<int>* reportIDs) { uint32_t reportID = IOHIDElementGetReportID(element); if (reportID) { reportIDs->insert(reportID); } CFArrayRef children = IOHIDElementGetChildren(element); if (!children) { return; } CFIndex childrenCount = CFArrayGetCount(children); for (CFIndex j = 0; j < childrenCount; ++j) { const IOHIDElementRef child = static_cast<IOHIDElementRef>( const_cast<void*>(CFArrayGetValueAtIndex(children, j))); GetReportIds(child, reportIDs); } } void GetCollectionInfos(IOHIDDeviceRef device, bool* has_report_id, std::vector<HidCollectionInfo>* top_level_collections) { STLClearObject(top_level_collections); CFMutableDictionaryRef collections_filter = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); const int kCollectionTypeValue = kIOHIDElementTypeCollection; CFNumberRef collection_type_id = CFNumberCreate( kCFAllocatorDefault, kCFNumberIntType, &kCollectionTypeValue); CFDictionarySetValue( collections_filter, CFSTR(kIOHIDElementTypeKey), collection_type_id); CFRelease(collection_type_id); CFArrayRef collections = IOHIDDeviceCopyMatchingElements( device, collections_filter, kIOHIDOptionsTypeNone); CFIndex collectionsCount = CFArrayGetCount(collections); *has_report_id = false; for (CFIndex i = 0; i < collectionsCount; i++) { const IOHIDElementRef collection = static_cast<IOHIDElementRef>( const_cast<void*>(CFArrayGetValueAtIndex(collections, i))); // Top-Level Collection has no parent if (IOHIDElementGetParent(collection) == 0) { HidCollectionInfo collection_info; HidUsageAndPage::Page page = static_cast<HidUsageAndPage::Page>( IOHIDElementGetUsagePage(collection)); uint16_t usage = IOHIDElementGetUsage(collection); collection_info.usage = HidUsageAndPage(usage, page); // Explore children recursively and retrieve their report IDs GetReportIds(collection, &collection_info.report_ids); if (collection_info.report_ids.size() > 0) { *has_report_id = true; } top_level_collections->push_back(collection_info); } } } } // namespace HidServiceMac::HidServiceMac() { DCHECK(thread_checker_.CalledOnValidThread()); message_loop_ = base::MessageLoopProxy::current(); DCHECK(message_loop_); hid_manager_.reset(IOHIDManagerCreate(NULL, 0)); if (!hid_manager_) { LOG(ERROR) << "Failed to initialize HidManager"; return; } DCHECK(CFGetTypeID(hid_manager_) == IOHIDManagerGetTypeID()); IOHIDManagerOpen(hid_manager_, kIOHIDOptionsTypeNone); IOHIDManagerSetDeviceMatching(hid_manager_, NULL); // Enumerate all the currently known devices. Enumerate(); // Register for plug/unplug notifications. StartWatchingDevices(); } HidServiceMac::~HidServiceMac() { StopWatchingDevices(); } void HidServiceMac::StartWatchingDevices() { DCHECK(thread_checker_.CalledOnValidThread()); IOHIDManagerRegisterDeviceMatchingCallback( hid_manager_, &AddDeviceCallback, this); IOHIDManagerRegisterDeviceRemovalCallback( hid_manager_, &RemoveDeviceCallback, this); IOHIDManagerScheduleWithRunLoop( hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); } void HidServiceMac::StopWatchingDevices() { DCHECK(thread_checker_.CalledOnValidThread()); if (!hid_manager_) return; IOHIDManagerUnscheduleFromRunLoop( hid_manager_, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerClose(hid_manager_, kIOHIDOptionsTypeNone); } void HidServiceMac::AddDeviceCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef hid_device) { DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); // Claim ownership of the device. CFRetain(hid_device); HidServiceMac* service = HidServiceFromContext(context); service->message_loop_->PostTask(FROM_HERE, base::Bind(&HidServiceMac::PlatformAddDevice, base::Unretained(service), base::Unretained(hid_device))); } void HidServiceMac::RemoveDeviceCallback(void* context, IOReturn result, void* sender, IOHIDDeviceRef hid_device) { DCHECK(CFRunLoopGetMain() == CFRunLoopGetCurrent()); HidServiceMac* service = HidServiceFromContext(context); service->message_loop_->PostTask( FROM_HERE, base::Bind(&HidServiceMac::PlatformRemoveDevice, base::Unretained(service), base::Unretained(hid_device))); } void HidServiceMac::Enumerate() { DCHECK(thread_checker_.CalledOnValidThread()); HidDeviceList devices; EnumerateHidDevices(hid_manager_, &devices); for (HidDeviceList::const_iterator iter = devices.begin(); iter != devices.end(); ++iter) { IOHIDDeviceRef hid_device = *iter; PlatformAddDevice(hid_device); } } void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef hid_device) { // Note that our ownership of hid_device is implied if calling this method. // It is balanced in PlatformRemoveDevice. DCHECK(thread_checker_.CalledOnValidThread()); HidDeviceInfo device_info; device_info.device_id = hid_device; device_info.vendor_id = GetHidIntProperty(hid_device, CFSTR(kIOHIDVendorIDKey)); device_info.product_id = GetHidIntProperty(hid_device, CFSTR(kIOHIDProductIDKey)); device_info.product_name = GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey)); device_info.serial_number = GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey)); GetCollectionInfos(hid_device, &device_info.has_report_id, &device_info.collections); device_info.max_input_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey)); if (device_info.has_report_id && device_info.max_input_report_size > 0) { device_info.max_input_report_size--; } device_info.max_output_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey)); if (device_info.has_report_id && device_info.max_output_report_size > 0) { device_info.max_output_report_size--; } device_info.max_feature_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey)); if (device_info.has_report_id && device_info.max_feature_report_size > 0) { device_info.max_feature_report_size--; } AddDevice(device_info); } void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef hid_device) { DCHECK(thread_checker_.CalledOnValidThread()); RemoveDevice(hid_device); CFRelease(hid_device); } scoped_refptr<HidConnection> HidServiceMac::Connect( const HidDeviceId& device_id) { DCHECK(thread_checker_.CalledOnValidThread()); HidDeviceInfo device_info; if (!GetDeviceInfo(device_id, &device_info)) return NULL; return scoped_refptr<HidConnection>(new HidConnectionMac(device_info)); } } // namespace device