HIDManager.m   [plain text]

//
// HIDManager.m
// HID
//
// Created by dekom on 10/31/17.
//

#import <Foundation/Foundation.h>
#import <IOKit/hid/IOHIDManager.h>
#import "HIDManager.h"
#import "HIDDevicePrivate.h"
#import "HIDElementPrivate.h"
#import <os/assumes.h>

@implementation HIDManager {
IOHIDManagerRef _manager;
HIDManagerElementHandler _elementHandler;
HIDDeviceHandler _deviceNotificationHandler;
HIDReportHandler _inputReportHandler;
HIDBlock _cancelHandler;
}

- (instancetype)init
{
self = [super init];

if (!self) {
return self;
}

_manager = IOHIDManagerCreate(kCFAllocatorDefault, 0);

if (!_manager) {
return nil;
}

return self;
}

- (instancetype)initWithOptions:(HIDManagerOptions)options
{
self = [super init];

if (!self) {
return self;
}

_manager = IOHIDManagerCreate(kCFAllocatorDefault, (IOOptionBits)options);

if (!_manager) {
return nil;
}

return self;
}

- (void)dealloc
{
if (_manager) {
CFRelease(_manager);
}
}

- (NSString *)description {
return [NSString stringWithFormat:@"%@", _manager];
}

- (NSArray<HIDDevice *> *)devices
{
return [(NSSet *)CFBridgingRelease(IOHIDManagerCopyDevices(_manager))
allObjects];
}

- (id)propertyForKey:(NSString *)key
{
return (__bridge id)IOHIDManagerGetProperty(_manager,
(__bridge CFStringRef)key);
}

- (BOOL)setProperty:(id)value forKey:(NSString *)key
{
return IOHIDManagerSetProperty(_manager,
(__bridge CFStringRef)key,
(__bridge CFTypeRef)value);
}

- (void)setInputElementMatching:(id)matching
{
os_assert([matching isKindOfClass:[NSDictionary class]] ||
[matching isKindOfClass:[NSArray class]],
"Unknown matching criteria: %@", matching);

if ([matching isKindOfClass:[NSDictionary class]]) {
CFDictionaryRef matchDict = (__bridge CFDictionaryRef)matching;

if (((NSDictionary *)matching).count) {
IOHIDManagerSetInputValueMatching(_manager, matchDict);
} else {
IOHIDManagerSetInputValueMatching(_manager, nil);
}
} else if ([matching isKindOfClass:[NSArray class]]) {
CFArrayRef matchArray = (__bridge CFArrayRef)matching;

if (((NSArray *)matching).count) {
IOHIDManagerSetInputValueMatchingMultiple(_manager, matchArray);
} else {
IOHIDManagerSetInputValueMatchingMultiple(_manager, nil);
}
}
}

static void inputValueCallback(void *context, IOReturn result __unused,
void *sender, IOHIDValueRef value)
{
HIDManager *me = (__bridge HIDManager *)context;
HIDElement *element = (__bridge HIDElement *)IOHIDValueGetElement(value);
element.valueRef = value;

(me->_elementHandler)((__bridge HIDDevice *)sender, element);
}

- (void)setInputElementHandler:(HIDManagerElementHandler)handler
{
os_assert(!_elementHandler, "Input element handler already set");
_elementHandler = handler;
IOHIDManagerRegisterInputValueCallback(_manager,
inputValueCallback,
(__bridge void *)self);
}

- (void)setDeviceMatching:(id)matching
{
os_assert([matching isKindOfClass:[NSDictionary class]] ||
[matching isKindOfClass:[NSArray class]],
"Unknown matching criteria: %@", matching);

if ([matching isKindOfClass:[NSDictionary class]]) {
CFDictionaryRef matchDict = (__bridge CFDictionaryRef)matching;

if (((NSDictionary *)matching).count) {
IOHIDManagerSetDeviceMatching(_manager, matchDict);
} else {
IOHIDManagerSetDeviceMatching(_manager, nil);
}
} else if ([matching isKindOfClass:[NSArray class]]) {
CFArrayRef matchArray = (__bridge CFArrayRef)matching;

if (((NSArray *)matching).count) {
IOHIDManagerSetDeviceMatchingMultiple(_manager, matchArray);
} else {
IOHIDManagerSetDeviceMatchingMultiple(_manager, nil);
}
}
}

static void deviceAddedCallback(void *context __unused,
IOReturn result __unused,
void *sender __unused,
IOHIDDeviceRef device)
{
HIDManager *me = (__bridge HIDManager *)context;
(me->_deviceNotificationHandler)((__bridge HIDDevice *)device, true);
}

static void deviceRemovedCallback(void *context,
IOReturn result __unused,
void *sender __unused,
IOHIDDeviceRef device)
{
HIDManager *me = (__bridge HIDManager *)context;

(me->_deviceNotificationHandler)((__bridge HIDDevice *)device, false);
}

- (void)setDeviceNotificationHandler:(HIDDeviceHandler)handler
{
os_assert(!_deviceNotificationHandler, "Device notification handler already set");
_deviceNotificationHandler = handler;

IOHIDManagerRegisterDeviceMatchingCallback(_manager,
deviceAddedCallback,
(__bridge void *)self);

IOHIDManagerRegisterDeviceRemovalCallback(_manager,
deviceRemovedCallback,
(__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)
{
HIDManager *me = (__bridge HIDManager *)context;
NSData *data = [[NSData alloc] initWithBytesNoCopy:report
length:reportLength
freeWhenDone:NO];

(me->_inputReportHandler)((__bridge HIDDevice *)sender,
timeStamp,
(HIDReportType)type,
reportID,
data);
}

- (void)setInputReportHandler:(HIDReportHandler)handler
{
os_assert(!_inputReportHandler, "Input report handler already set");
_inputReportHandler = handler;

IOHIDManagerRegisterInputReportWithTimeStampCallback(_manager,
inputReportCallback,
(__bridge void *)self);
}

- (void)setCancelHandler:(HIDBlock)handler
{
_cancelHandler = handler;
}

- (void)setDispatchQueue:(dispatch_queue_t)queue
{
IOHIDManagerSetDispatchQueue(_manager, queue);
}

- (void)open
{
IOHIDManagerOpen(_manager, 0);
}

- (void)close
{
IOHIDManagerClose(_manager, 0);
}

- (void)activate
{
IOHIDManagerSetCancelHandler(_manager, ^{
// Block captures reference to self while cancellation hasn't completed.
if (self->_cancelHandler) {
self->_cancelHandler();
self->_cancelHandler = nil;
}
});

IOHIDManagerActivate(_manager);
}

- (void)cancel
{
IOHIDManagerCancel(_manager);
}

@end