// 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 <string> #include <vector> #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop_proxy.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/threading/thread_restrictions.h" #include "device/hid/hid_connection_mac.h" #include "device/hid/hid_utils_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); } } // 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.input_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey)); device_info.output_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey)); device_info.feature_report_size = GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey)); CFTypeRef deviceUsagePairsRaw = IOHIDDeviceGetProperty(hid_device, CFSTR(kIOHIDDeviceUsagePairsKey)); CFArrayRef deviceUsagePairs = base::mac::CFCast<CFArrayRef>(deviceUsagePairsRaw); CFIndex deviceUsagePairsCount = CFArrayGetCount(deviceUsagePairs); for (CFIndex i = 0; i < deviceUsagePairsCount; i++) { CFDictionaryRef deviceUsagePair = base::mac::CFCast<CFDictionaryRef>( CFArrayGetValueAtIndex(deviceUsagePairs, i)); CFNumberRef usage_raw = base::mac::CFCast<CFNumberRef>( CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsageKey))); uint16_t usage; CFNumberGetValue(usage_raw, kCFNumberSInt32Type, &usage); CFNumberRef page_raw = base::mac::CFCast<CFNumberRef>( CFDictionaryGetValue(deviceUsagePair, CFSTR(kIOHIDDeviceUsagePageKey))); HidUsageAndPage::Page page; CFNumberGetValue(page_raw, kCFNumberSInt32Type, &page); device_info.usages.push_back(HidUsageAndPage(usage, page)); } device_info.product_name = GetHidStringProperty(hid_device, CFSTR(kIOHIDProductKey)); device_info.serial_number = GetHidStringProperty(hid_device, CFSTR(kIOHIDSerialNumberKey)); 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