//
// HIDDevice.m
// HID
//
// Created by dekom on 10/9/17.
//
#import <Foundation/Foundation.h>
#import <IOKit/hid/IOHIDDevice.h>
#import "HIDDevicePrivate.h"
#import "HIDElementPrivate.h"
#import "NSError+IOReturn.h"
#import "HIDTransaction.h"
#import <os/assumes.h>
@implementation HIDDevice (HIDFramework)
- (instancetype)initWithService:(io_service_t)service
{
return (__bridge_transfer HIDDevice *)IOHIDDeviceCreate(kCFAllocatorDefault,
service);
}
- (id)propertyForKey:(NSString *)key
{
return (__bridge id)IOHIDDeviceGetProperty((__bridge IOHIDDeviceRef)self,
(__bridge CFStringRef)key);
}
- (BOOL)setProperty:(id)value forKey:(NSString *)key
{
return IOHIDDeviceSetProperty((__bridge IOHIDDeviceRef)self,
(__bridge CFStringRef)key,
(__bridge CFTypeRef)value);
}
- (BOOL)conformsToUsagePage:(NSInteger)usagePage usage:(NSInteger)usage
{
return IOHIDDeviceConformsTo((__bridge IOHIDDeviceRef)self,
(uint32_t)usagePage,
(uint32_t)usage);
}
- (NSArray *)elementsMatching:(NSDictionary *)matching
{
return (NSArray *)CFBridgingRelease(IOHIDDeviceCopyMatchingElements(
(__bridge IOHIDDeviceRef)self,
(__bridge CFDictionaryRef)matching,
0));
}
- (BOOL)setReport:(const void *)report
reportLength:(NSInteger)reportLength
withIdentifier:(NSInteger)reportID
forType:(HIDReportType)reportType
error:(out NSError **)outError
{
IOReturn ret = IOHIDDeviceSetReport((__bridge IOHIDDeviceRef)self,
(IOHIDReportType)reportType,
reportID,
(uint8_t *)report,
reportLength);
if (ret != kIOReturnSuccess && outError) {
*outError = [NSError errorWithIOReturn:ret];
}
return (ret == kIOReturnSuccess);
}
- (BOOL)getReport:(void *)report
reportLength:(NSInteger *)reportLength
withIdentifier:(NSInteger)reportID
forType:(HIDReportType)reportType
error:(out NSError **)outError
{
CFIndex length = (CFIndex)*reportLength;
IOReturn ret = IOHIDDeviceGetReport((__bridge IOHIDDeviceRef)self,
(IOHIDReportType)reportType,
reportID,
(uint8_t *)report,
&length);
if (ret != kIOReturnSuccess && outError) {
*outError = [NSError errorWithIOReturn:ret];
}
*reportLength = (NSInteger)length;
return (ret == kIOReturnSuccess);
}
- (BOOL)commitElements:(NSArray<HIDElement *> *)elements
direction:(HIDDeviceCommitDirection)direction
error:(out NSError **)outError
{
HIDTransaction *transaction = nil;
if (!_device.transaction) {
_device.transaction = (void *)CFBridgingRetain([[HIDTransaction alloc]
initWithDevice:self]);
}
transaction = (__bridge HIDTransaction *)_device.transaction;
if (direction == HIDDeviceCommitDirectionIn) {
transaction.direction = HIDTransactionDirectionTypeInput;
} else {
transaction.direction = HIDTransactionDirectionTypeOutput;
}
return [transaction commitElements:elements error:outError];
}
- (void)setInputElementMatching:(id)matching
{
os_assert([matching isKindOfClass:[NSDictionary class]] ||
[matching isKindOfClass:[NSArray class]],
"Unknown matching criteria:
if ([matching isKindOfClass:[NSDictionary class]]) {
CFDictionaryRef matchDict = (__bridge CFDictionaryRef)matching;
if (((NSDictionary *)matching).count) {
IOHIDDeviceSetInputValueMatching((__bridge IOHIDDeviceRef)self,
matchDict);
} else {
IOHIDDeviceSetInputValueMatching((__bridge IOHIDDeviceRef)self,
nil);
}
} else if ([matching isKindOfClass:[NSArray class]]) {
CFArrayRef matchArray = (__bridge CFArrayRef)matching;
if (((NSArray *)matching).count) {
IOHIDDeviceSetInputValueMatchingMultiple(
(__bridge IOHIDDeviceRef)self,
matchArray);
} else {
IOHIDDeviceSetInputValueMatchingMultiple(
(__bridge IOHIDDeviceRef)self,
nil);
}
}
}
static void inputValueCallback(void *context, IOReturn result __unused,
void *sender __unused, IOHIDValueRef value)
{
HIDDevice *me = (__bridge HIDDevice *)context;
HIDElement *element = (__bridge HIDElement *)IOHIDValueGetElement(value);
element.valueRef = value;
if (me->_device.elementHandler) {
((__bridge HIDDeviceElementHandler)me->_device.elementHandler)(element);
}
}
- (void)setInputElementHandler:(HIDDeviceElementHandler)handler
{
os_assert(!_device.elementHandler, "Input element handler already set");
_device.elementHandler = (void *)Block_copy((__bridge const void *)handler);
IOHIDDeviceRegisterInputValueCallback((__bridge IOHIDDeviceRef)self,
inputValueCallback,
(__bridge void *)self);
}
static void batchInputValueCallback(void *context, IOReturn result __unused,
void *sender __unused, IOHIDValueRef value)
{
HIDDevice *me = (__bridge HIDDevice *)context;
HIDElement *element = (__bridge HIDElement *)IOHIDValueGetElement(value);
element.valueRef = value;
NSMutableArray *array = (__bridge NSMutableArray *)me->_device.batchElements;
if (element.type == kIOHIDElementTypeInput_NULL) {
if (me->_device.elementHandler) {
((__bridge HIDDeviceBatchElementHandler)me->_device.elementHandler)(array);
}
[array removeAllObjects];
} else {
[array addObject:element];
}
}
- (void)setBatchInputElementHandler:(HIDDeviceBatchElementHandler)handler
{
os_assert(!_device.elementHandler, "Input element handler already set");
_device.elementHandler = (void *)Block_copy((__bridge const void *)handler);
_device.batchElements = CFArrayCreateMutable(kCFAllocatorDefault,
0,
&kCFTypeArrayCallBacks);
IOHIDDeviceRegisterInputValueCallback((__bridge IOHIDDeviceRef)self,
batchInputValueCallback,
(__bridge void *)self);
}
static void removalCallback(void *context, IOReturn result __unused,
void *sender __unused)
{
HIDDevice *me = (__bridge HIDDevice *)context;
if (me->_device.removalHandler) {
((__bridge HIDBlock)me->_device.removalHandler)();
Block_release(me->_device.removalHandler);
me->_device.removalHandler = nil;
}
}
- (void)setRemovalHandler:(HIDBlock)handler
{
os_assert(!_device.removalHandler, "Removal handler already set");
_device.removalHandler = (void *)Block_copy((__bridge const void *)handler);
IOHIDDeviceRegisterRemovalCallback((__bridge IOHIDDeviceRef)self,
removalCallback,
(__bridge void *)self);
}
static void inputReportCallback(void *context,
IOReturn result __unused,
void *sender,
IOHIDReportType type,
uint32_t reportID,
uint8_t *report,
CFIndex reportLength,
uint64_t timeStamp)
{
HIDDevice *me = (__bridge HIDDevice *)context;
NSData *data = [[NSData alloc] initWithBytesNoCopy:report
length:reportLength
freeWhenDone:NO];
if (me->_device.inputReportHandler) {
((__bridge HIDReportHandler)me->_device.inputReportHandler)(
(__bridge HIDDevice *)sender,
timeStamp,
(HIDReportType)type,
reportID,
data);
}
}
- (void)setInputReportHandler:(HIDReportHandler)handler
{
NSUInteger bufferSize = 1;
os_assert(!_device.inputReportHandler, "Input report handler already set");
_device.inputReportHandler = (void *)Block_copy(
(__bridge const void *)handler);
NSNumber *reportSize = [self propertyForKey:@kIOHIDMaxInputReportSizeKey];
if (reportSize != nil) {
bufferSize = reportSize.unsignedIntegerValue;
}
if (!_device.reportBuffer) {
_device.reportBuffer = CFDataCreateMutable(kCFAllocatorDefault,
bufferSize);
CFDataSetLength(_device.reportBuffer, bufferSize);
}
IOHIDDeviceRegisterInputReportWithTimeStampCallback(
(__bridge IOHIDDeviceRef)self,
(uint8_t *)CFDataGetMutableBytePtr(_device.reportBuffer),
CFDataGetLength(_device.reportBuffer),
inputReportCallback,
(__bridge void *)self);
}
- (void)setCancelHandler:(HIDBlock)handler
{
IOHIDDeviceSetCancelHandler((__bridge IOHIDDeviceRef)self, handler);
}
- (void)setDispatchQueue:(dispatch_queue_t)queue
{
IOHIDDeviceSetDispatchQueue((__bridge IOHIDDeviceRef)self, queue);
}
- (void)open
{
IOHIDDeviceOpen((__bridge IOHIDDeviceRef)self, 0);
}
- (BOOL)openSeize: (out NSError **)outError
{
IOReturn status = IOHIDDeviceOpen((__bridge IOHIDDeviceRef)self,
kIOHIDOptionsTypeSeizeDevice);
if (status && outError) {
*outError = [NSError errorWithIOReturn:status];
}
return (status == kIOReturnSuccess);
}
- (void)close
{
IOHIDDeviceClose((__bridge IOHIDDeviceRef)self, 0);
}
- (void)activate
{
if (_device.batchElements) {
NSArray *oldMatch = (__bridge NSArray *)_device.inputMatchingMultiple;
NSMutableArray *newMatch = nil;
if (oldMatch) {
newMatch = [NSMutableArray arrayWithArray:oldMatch];
} else {
newMatch = [NSMutableArray arrayWithObjects:
@{@kIOHIDElementTypeKey: @(kIOHIDElementTypeInput_Misc)},
@{@kIOHIDElementTypeKey: @(kIOHIDElementTypeInput_Button)},
@{@kIOHIDElementTypeKey: @(kIOHIDElementTypeInput_Axis)},
@{@kIOHIDElementTypeKey: @(kIOHIDElementTypeInput_ScanCodes)},
nil];
}
[newMatch addObject:@{@kIOHIDElementTypeKey:
@(kIOHIDElementTypeInput_NULL)}];
[self setInputElementMatching:newMatch];
}
IOHIDDeviceActivate((__bridge IOHIDDeviceRef)self);
}
- (void)cancel
{
IOHIDDeviceCancel((__bridge IOHIDDeviceRef)self);
}
- (io_service_t)service
{
return IOHIDDeviceGetService((__bridge IOHIDDeviceRef)self);
}
@end