blob: 98bfd9231a9035ac38fca78056e87112072790f5 [file] [log] [blame]
// 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_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) {
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)));
uint32_t reportID = IOHIDElementGetReportID(child);
if (reportID) {
reportIDs.insert(reportID);
}
GetReportIds(child, reportIDs);
}
}
void GetCollectionInfos(IOHIDDeviceRef device,
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);
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);
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.collections);
device_info.max_input_report_size =
GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxInputReportSizeKey));
device_info.max_output_report_size =
GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxOutputReportSizeKey));
device_info.max_feature_report_size =
GetHidIntProperty(hid_device, CFSTR(kIOHIDMaxFeatureReportSizeKey));
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