IOHIDDisplaySessionFilter.m   [plain text]

//
// HIDDisplaySessionFilter.m
// HIDDisplaySessionFilter
//
// Created by AB on 1/25/19.
//

#import "IOHIDDisplaySessionFilter.h"
#import <os/log.h>
#import <HIDDisplay/HIDDisplayPresetInterfacePrivate.h>
#import <HIDDisplay/HIDDisplayPresetInterface.h>
#import <AssertMacros.h>
#import <IOKit/hid/AppleHIDUsageTables.h>
#import <IOKit/hid/IOHIDKeys.h>
#import <IOKit/hid/IOHIDManager.h>
#import <IOKit/hid/IOHIDDevice.h>

static os_log_t HIDDisplaySessionFilterLog (void);

@interface HIDDisplaySessionFilter (HIDDisplaySessionFilterPrivate)

-(void) handleDeviceMatchCallback:(IOHIDDeviceRef) device;
-(void) handleDeviceRemovalCallback:(IOHIDDeviceRef) device;

@end

os_log_t HIDDisplaySessionFilterLog (void)
{
static os_log_t slog = NULL;
static dispatch_once_t sonceToken;
dispatch_once(&sonceToken, ^{
slog = os_log_create("com.apple.HIDDisplaySessionFilter", "default");
});

return slog;
}

static void __deviceMatchingCallback(void * _Nullable context, IOReturn result, void * _Nullable __unused sender, IOHIDDeviceRef device)
{
HIDDisplaySessionFilter *selfRef = (__bridge HIDDisplaySessionFilter*)context;

if (!selfRef || result != kIOReturnSuccess) {
return;
}

[selfRef handleDeviceMatchCallback:device];
}

static void __deviceRemovalCallback(void * _Nullable context, IOReturn result, void * _Nullable __unused sender, IOHIDDeviceRef device)
{
HIDDisplaySessionFilter *selfRef = (__bridge HIDDisplaySessionFilter*)context;

if (!selfRef || result != kIOReturnSuccess) {
return;
}

[selfRef handleDeviceRemovalCallback:device];
}

@implementation HIDDisplaySessionFilter
{
IOHIDManagerRef _manager;
NSDictionary *_matching;
NSMutableArray *_debugInfo;
NSThread *_thread;
CFRunLoopRef _runLoop;

}
-(nullable instancetype) init
{
self = [super init];
require(self, exit);

_manager = IOHIDManagerCreate (kCFAllocatorDefault, kIOHIDOptionsTypeNone);
require(_manager, exit);

_matching = @{@kIOHIDDeviceUsagePairsKey : @[@{
@kIOHIDDeviceUsagePageKey : @(kHIDPage_AppleVendor),
@kIOHIDDeviceUsageKey: @(kHIDUsage_AppleVendor_Display)
}]};

_debugInfo = [[NSMutableArray alloc] init];

IOHIDManagerSetDeviceMatching(_manager, (__bridge CFDictionaryRef)_matching);

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

IOHIDManagerRegisterDeviceRemovalCallback(_manager, __deviceRemovalCallback, (__bridge void*)self);

exit:
return self; // nil can cause unwanted condition in plugin
}

-(BOOL) open
{
__weak HIDDisplaySessionFilter *weakSelf = self;

if (!_manager) {
return NO;
}

[NSThread detachNewThreadWithBlock:^{

__strong HIDDisplaySessionFilter *strongSelf = weakSelf;

if (!strongSelf) {
return;
}

strongSelf->_runLoop = CFRunLoopGetCurrent();

IOHIDManagerScheduleWithRunLoop(strongSelf->_manager, strongSelf->_runLoop, kCFRunLoopDefaultMode);

IOHIDManagerOpen(strongSelf->_manager, kIOHIDOptionsTypeNone);

CFRunLoopRun();

}];

return YES;
}

-(void) handleDeviceMatchCallback:(IOHIDDeviceRef) device
{
NSNumber *uniqueID = nil;

HIDDisplayPresetInterface *displayInterface = [[HIDDisplayPresetInterface alloc] initWithMatching:_matching];

uniqueID = (__bridge NSNumber*)IOHIDDeviceGetProperty( device, CFSTR(kIOHIDUniqueIDKey) );

// shouldn't expect failure here, but just for case, deviceMatchCallback is called and the device disappear and is not detected by framework
require_action(displayInterface, exit, os_log_error(HIDDisplaySessionFilterLog(), "No valid HID device for matching %{public}@",_matching));

[self setPresetIndexForDevice:displayInterface uniqueID:uniqueID];

exit:
return;
}

-(void) handleDeviceRemovalCallback:(IOHIDDeviceRef) device
{
NSNumber *uniqueID = (__bridge NSNumber*)IOHIDDeviceGetProperty( device, CFSTR(kIOHIDUniqueIDKey) );

@synchronized (self) {
[_debugInfo removeObject:uniqueID];
}
}

-(BOOL) setPresetIndexForDevice:(HIDDisplayPresetInterface*) displayInterface uniqueID:(NSNumber*) uniqueID
{
NSError *err = nil;
NSInteger factoryDefaultPresetIndex = -1;
NSInteger activePresetIndex = -1;
BOOL ret = NO;

factoryDefaultPresetIndex = [displayInterface getFactoryDefaultPresetIndex:&err];
require_action(factoryDefaultPresetIndex != -1, exit, os_log_error(HIDDisplaySessionFilterLog(), "Failed to get factory default preset index with %@",err));

activePresetIndex = [displayInterface getActivePresetIndex:&err];
require_action(activePresetIndex != -1, exit, os_log_error(HIDDisplaySessionFilterLog(), "Failed to get active preset index with %@",err));

if (factoryDefaultPresetIndex != activePresetIndex) {

os_log(HIDDisplaySessionFilterLog(),"Setting active preset index (%ld) to factory default preset index (%ld)", activePresetIndex, factoryDefaultPresetIndex);

require_action([displayInterface setActivePresetIndex:factoryDefaultPresetIndex error:&err], exit, os_log_error(HIDDisplaySessionFilterLog(), "Failed to set active preset to factory default preset with error %@",err));
}

ret = YES;

exit:

@synchronized (self) {

if (ret) {
[_debugInfo addObject:uniqueID];
}
}

return ret;
}

-(void) close
{

if (!_runLoop) return;

__weak HIDDisplaySessionFilter *weakSelf = self;

CFRunLoopPerformBlock(_runLoop, kCFRunLoopDefaultMode, ^{

__strong HIDDisplaySessionFilter *strongSelf = weakSelf;

if (!strongSelf) {
return;
}

if (strongSelf->_manager) {

IOHIDManagerUnscheduleFromRunLoop(strongSelf->_manager, strongSelf->_runLoop, kCFRunLoopDefaultMode);
IOHIDManagerClose(strongSelf->_manager, kIOHIDOptionsTypeNone);
}
CFRunLoopStop(strongSelf->_runLoop);

});
}

-(NSDictionary*) serialize
{
NSMutableDictionary *ret = [[NSMutableDictionary alloc] init];

@synchronized (self) {

ret[@"Class"] = @"HIDDisplaySessionFilter";
ret[@"Status"] = self->_debugInfo;
}

return ret;
}
-(CFDictionaryRef) getProperty
{
return (__bridge CFDictionaryRef)[self serialize];
}

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