HIDDisplayDevice.m [plain text]
//
// HIDDisplayDevice.m
// HIDDisplay
//
// Created by AB on 1/10/19.
//
#import "HIDDisplayDevice.h"
#import "HIDDisplayDevicePreset.h"
#import "HIDElement.h"
#import "HIDElementPrivate.h"
#import "HIDDisplayCAPI.h"
#import "HIDDisplayDevicePresetPrivate.h"
#import "HIDDisplayPrivate.h"
#import "HIDDisplayDevicePrivate.h"
#import <IOKit/hid/IOHIDKeys.h>
#import <IOKit/hid/IOHIDManager.h>
#import <IOKit/hid/IOHIDDevice.h>
#import <IOKit/hid/AppleHIDUsageTables.h>
#import <IOKit/hid/IOHIDLibPrivate.h>
#import <IOKit/usb/USBSpec.h>
#import <AssertMacros.h>
#import <IOKit/hid/IOHIDLib.h>
#import <TargetConditionals.h>
#import <IOKit/IOKitKeys.h>
@implementation HIDDisplayDevice
{
IOHIDManagerRef _manager;
IOHIDDeviceRef _deviceRef;
NSMutableDictionary<NSNumber*,HIDElement*> *_usageElementMap;
IOHIDTransactionRef _transaction;
}
-(nullable instancetype) initWithContainerID:(NSString*) containerID
{
self = [super init];
if (!self) {
return nil;
}
_containerID = containerID;
#if !TARGET_OS_IPHONE || TARGET_OS_IOSMAC
NSDictionary *matching = @{@kIOPropertyMatchKey : @{@kUSBDeviceContainerID : _containerID}};
#else
NSDictionary *matching = @{@kIOPropertyMatchKey : @{@kUSBContainerID : _containerID}};
#endif
if (![self createDeviceWithMatching:matching]) {
return nil;
}
return self;
}
-(nullable instancetype) initWithMatching:(NSDictionary*) matching
{
self = [super init];
if (!self) {
return nil;
}
if (![self createDeviceWithMatching:matching]) {
return nil;
}
return self;
}
-(BOOL) createDeviceWithMatching:(NSDictionary*) matching
{
if (![self discoverHIDDevice:matching]) {
os_log_error(HIDDisplayLog(),"Failed to discover HID Display device for matching return NO;
}
if (![self discoverDeviceElements]) {
os_log_error(HIDDisplayLog(),"Failed to discover HID Elements for device
return NO;
}
_transaction = IOHIDTransactionCreate(kCFAllocatorDefault, self.device, kIOHIDTransactionDirectionTypeOutput, kIOHIDOptionsTypeNone);
if (!_transaction) {
return NO;
}
[self createPresets];
return YES;
}
-(void) createPresets
{
NSMutableArray<HIDDisplayDevicePreset*> *presets = [[NSMutableArray alloc] init];
NSInteger presetCount = 0;
HIDElement *presetCountElement = [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayCurrentPresetIndex)];
if (presetCountElement) {
presetCount = presetCountElement.logicalMax;
}
for (NSInteger i=0; i < presetCount; i++) {
HIDDisplayDevicePreset *preset = [[HIDDisplayDevicePreset alloc] initWithDisplayDevice:self index:i];
if (preset) {
[presets addObject:preset];
}
}
_presets = presets;
}
-(void) dealloc
{
if (_manager) {
IOHIDManagerClose(_manager, 0);
CFRelease(_manager);
_manager = nil;
}
if (_transaction) {
CFRelease(_transaction);
_transaction = nil;
}
if (self.device) {
CFRelease(self.device);
self.device = nil;
}
}
-(BOOL) discoverHIDDevice:(NSDictionary*) matching
{
BOOL ret = NO;
NSSet *devices = nil;
NSArray *tmp = nil;
_manager = IOHIDManagerCreate (kCFAllocatorDefault, kIOHIDOptionsTypeNone);
require(_manager, exit);
IOHIDManagerSetDeviceMatching(_manager, (__bridge CFDictionaryRef)matching);
IOHIDManagerOpen(_manager, kIOHIDOptionsTypeNone);
devices = (__bridge_transfer NSSet*)IOHIDManagerCopyDevices(_manager);
require(devices, exit);
tmp = [devices allObjects];
if (tmp.count == 1) {
self.device = (__bridge_retained IOHIDDeviceRef)[tmp objectAtIndex:0];
}
require(self.device, exit);
ret = YES;
exit:
return ret;
}
-(BOOL) discoverDeviceElements
{
NSDictionary *matching = @{@kIOHIDElementUsagePageKey: @(kHIDPage_AppleVendorDisplayPreset)};
CFArrayRef elements = IOHIDDeviceCopyMatchingElements(self.device, (__bridge CFDictionaryRef)matching, 0);
BOOL ret = NO;
require(elements, exit);
_usageElementMap = [[NSMutableDictionary alloc] init];
for (CFIndex i = 0; i < CFArrayGetCount(elements); i++ ) {
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
uint32_t usage = IOHIDElementGetUsage(element);
HIDElement *displayElement = [[HIDElement alloc] initWithObject:element];
if (displayElement) {
_usageElementMap[@(usage)] = displayElement;
ret = YES;
}
}
exit:
if (elements) {
CFRelease(elements);
}
return ret;
}
-(IOHIDDeviceRef) device
{
return _deviceRef;
}
-(void) setDevice:(IOHIDDeviceRef) device
{
_deviceRef = device;
}
-(HIDElement*) getHIDElementForUsage:(NSInteger) usage
{
if (!_usageElementMap) {
return nil;
}
return [_usageElementMap objectForKey:@(usage)];
}
-(NSInteger) getFactoryDefaultPresetIndex:(NSError**) error
{
NSInteger index = -1;
HIDElement *factoryDefaultIndexElement = _usageElementMap ? [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayFactoryDefaultPresetIndex)] : NULL;
if (factoryDefaultIndexElement) {
if ([self extract:@[factoryDefaultIndexElement] error:error]) {
index = factoryDefaultIndexElement.integerValue;
}
}
return index;
}
-(NSInteger) getActivePresetIndex:(NSError**) error
{
NSInteger index = -1;
HIDElement *activePresetIndexElement = _usageElementMap ? [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayActivePresetIndex)] : NULL;
if (activePresetIndexElement) {
if ([self extract:@[activePresetIndexElement] error:error]) {
index = activePresetIndexElement.integerValue;
}
}
return index;
}
-(BOOL) setActivePresetIndex:(NSInteger) index error:(NSError**) error
{
BOOL ret = YES;
if (index < 0 || (NSUInteger)index >= _presets.count) {
if (error) {
*error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:kIOReturnInvalid userInfo:nil];
}
return NO;
}
HIDDisplayDevicePreset *preset = _presets[index];
// check if preset can be set as active preset
if (preset.valid == 0) {
if (error) {
*error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:kIOReturnInvalid userInfo:nil];
}
os_log_error(HIDDisplayLog(),"[containerID: return NO;
}
HIDElement *activePresetIndexElement = _usageElementMap ? [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayActivePresetIndex)] : NULL;
if (activePresetIndexElement) {
activePresetIndexElement.integerValue = index;
ret = [self commit:@[activePresetIndexElement] error:error];
} else {
os_log_error(HIDDisplayLog(),"[containerID: }
return ret;
}
-(BOOL) setCurrentPresetIndex:(NSInteger) index error:(NSError**) error
{
BOOL ret = YES;
HIDElement *currentPresetIndexElement = _usageElementMap ? [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayCurrentPresetIndex)] : NULL;
if (currentPresetIndexElement) {
currentPresetIndexElement.integerValue = index;
ret = [self commit:@[currentPresetIndexElement] error:error];
}
return ret;
}
-(NSInteger) getCurrentPresetIndex:(NSError**) error
{
NSInteger index = -1;
HIDElement *currentPresetIndexElement = _usageElementMap ? [_usageElementMap objectForKey:@(kHIDUsage_AppleVendorDisplayCurrentPresetIndex)] : NULL;
if (currentPresetIndexElement) {
if ([self extract:@[currentPresetIndexElement] error:error]) {
index = currentPresetIndexElement.integerValue;
}
}
return index;
}
-(BOOL) commit:(NSArray<HIDElement*>*) elements error:(NSError**) error
{
BOOL ret = YES;
IOReturn kr = kIOReturnSuccess;
IOHIDTransactionClear(_transaction);
IOHIDTransactionSetDirection(_transaction, kIOHIDTransactionDirectionTypeOutput);
for (HIDElement *element in elements) {
IOHIDTransactionAddElement(_transaction,element.element);
IOHIDTransactionSetValue(_transaction,element.element, element.valueRef, kIOHIDOptionsTypeNone);
}
kr = IOHIDTransactionCommit(_transaction);
if (kr) {
ret = NO;
if (error) {
*error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:kr userInfo:nil];
}
os_log_error(HIDDisplayLog(),"[containerID:
return ret;
}
IOHIDTransactionClear(_transaction);
return ret;
}
-(BOOL) extract:(NSArray<HIDElement*>*) elements error:(NSError**) error
{
IOReturn kr = kIOReturnSuccess;
BOOL ret = YES;
IOHIDTransactionClear(_transaction);
IOHIDTransactionSetDirection(_transaction, kIOHIDTransactionDirectionTypeInput);
for (HIDElement *element in elements) {
IOHIDTransactionAddElement(_transaction,element.element);
}
kr = IOHIDTransactionCommit(_transaction);
if (kr) {
ret = NO;
if (error) {
*error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:kr userInfo:nil];
}
os_log_error(HIDDisplayLog(),"[containerID:
return ret;
}
for (HIDElement *element in elements) {
element.valueRef = IOHIDTransactionGetValue(_transaction,element.element,kIOHIDOptionsTypeNone);
}
IOHIDTransactionClear(_transaction);
return ret;
}
-(nullable NSArray<NSString*>*) capabilities
{
// check for container id for specific display
return @[(__bridge NSString*)kHIDDisplayPresetFieldWritableKey,
(__bridge NSString*)kHIDDisplayPresetFieldValidKey,
(__bridge NSString*)kHIDDisplayPresetFieldNameKey,
(__bridge NSString*)kHIDDisplayPresetFieldDescriptionKey,
(__bridge NSString*)kHIDDisplayPresetFieldDataBlockOneLengthKey,
(__bridge NSString*)kHIDDisplayPresetFieldDataBlockOneKey,
(__bridge NSString*)kHIDDisplayPresetFieldDataBlockTwoLengthKey,
(__bridge NSString*)kHIDDisplayPresetFieldDataBlockTwoKey,
(__bridge NSString*)kHIDDisplayPresetUniqueIDKey,
];
}
@end