From 402a1b679ced5422e46c7a5caeecc45e5ed878db Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 13 Mar 2013 12:23:11 +0100 Subject: [PATCH 1/1] Split actual IOKit HID interfacing off from NJDeviceController. --- Classes/NJDeviceController.h | 6 +- Classes/NJDeviceController.m | 136 ++++++++------------ Classes/NJHIDManager.h | 45 +++++++ Classes/NJHIDManager.m | 99 ++++++++++++++ Enjoyable.xcodeproj/project.pbxproj | 6 + Info.plist | 2 +- Resources/English.lproj/Localizable.strings | Bin 7060 -> 6424 bytes 7 files changed, 208 insertions(+), 86 deletions(-) create mode 100644 Classes/NJHIDManager.h create mode 100644 Classes/NJHIDManager.m diff --git a/Classes/NJDeviceController.h b/Classes/NJDeviceController.h index 71a0ed1..f49d647 100644 --- a/Classes/NJDeviceController.h +++ b/Classes/NJDeviceController.h @@ -6,12 +6,16 @@ // Copyright 2009 University of Otago. All rights reserved. // +#import "NJHIDManager.h" + @class NJDevice; @class NJInput; @class NJMappingsController; @class NJOutputController; -@interface NJDeviceController : NSObject { +@interface NJDeviceController : NSObject { IBOutlet NSOutlineView *outlineView; IBOutlet NJOutputController *outputController; IBOutlet NJMappingsController *mappingsController; diff --git a/Classes/NJDeviceController.m b/Classes/NJDeviceController.m index 2a1546c..610c07b 100644 --- a/Classes/NJDeviceController.m +++ b/Classes/NJDeviceController.m @@ -16,7 +16,7 @@ #import "NJEvents.h" @implementation NJDeviceController { - IOHIDManagerRef _hidManager; + NJHIDManager *_hidManager; NSTimer *_continuousOutputsTick; NSMutableArray *_continousOutputs; NSMutableArray *_devices; @@ -24,6 +24,7 @@ } #define EXPANDED_MEMORY_MAX_SIZE 100 +#define NSSTR(e) ((NSString *)CFSTR(e)) - (id)init { if ((self = [super init])) { @@ -35,10 +36,20 @@ expanded = @[]; _expanded = [[NSMutableArray alloc] initWithCapacity:MAX(16, _expanded.count)]; [_expanded addObjectsFromArray:expanded]; + + _hidManager = [[NJHIDManager alloc] initWithCriteria:@[ + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) }, + @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), + NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) } + ] + delegate:self]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(applicationDidFinishLaunching:) + selector:@selector(startHid) name:NSApplicationDidFinishLaunchingNotification object:nil]; @@ -53,12 +64,12 @@ // grabs. [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(closeHidIfDisabled:) + selector:@selector(stopHidIfDisabled:) name:NSApplicationDidResignActiveNotification object:nil]; [NSNotificationCenter.defaultCenter addObserver:self - selector:@selector(openHid) + selector:@selector(startHid) name:NSApplicationDidBecomeActiveNotification object:nil]; } @@ -68,7 +79,6 @@ - (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; [_continuousOutputsTick invalidate]; - [self closeHid]; } - (void)expandRecursive:(id )pathElement { @@ -131,14 +141,13 @@ [outputController focusKey]; } -static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender); - - if (controller.translatingEvents) { - [controller runOutputForDevice:device value:value]; - } else if ([NSApplication sharedApplication].mainWindow.isVisible) { - [controller showOutputForDevice:device value:value]; +- (void)hidManager:(NJHIDManager *)manager + valueChanged:(IOHIDValueRef)value + fromDevice:(IOHIDDeviceRef)device { + if (self.translatingEvents) { + [self runOutputForDevice:device value:value]; + } else { + [self showOutputForDevice:device value:value]; } } @@ -146,7 +155,8 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { for (int index = 1; ; index++) { BOOL available = YES; for (NJDevice *used in list) { - if ([used.productName isEqualToString:dev.productName] && used.index == index) { + if ([used.productName isEqualToString:dev.productName] + && used.index == index) { available = NO; break; } @@ -156,22 +166,16 @@ static int findAvailableIndex(NSArray *list, NJDevice *dev) { } } -- (void)addDeviceForDevice:(IOHIDDeviceRef)device { - IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void *)self); - NJDevice *dev = [[NJDevice alloc] initWithDevice:device]; - dev.index = findAvailableIndex(_devices, dev); - [_devices addObject:dev]; +- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device { + NJDevice *match = [[NJDevice alloc] initWithDevice:device]; + match.index = findAvailableIndex(_devices, match); + [_devices addObject:match]; [outlineView reloadData]; [self reexpandAll]; hidSleepingPrompt.hidden = YES; connectDevicePrompt.hidden = !!_devices.count; } -static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - [controller addDeviceForDevice:device]; -} - - (NJDevice *)findDeviceByRef:(IOHIDDeviceRef)device { for (NJDevice *dev in _devices) if (dev.device == device) @@ -179,12 +183,7 @@ static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDevi return nil; } -static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { - NJDeviceController *controller = (__bridge NJDeviceController *)ctx; - [controller removeDeviceForDevice:device]; -} - -- (void)removeDeviceForDevice:(IOHIDDeviceRef)device { +- (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device { NJDevice *match = [self findDeviceByRef:device]; IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL); if (match) { @@ -210,58 +209,34 @@ static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDD } } -#define NSSTR(e) ((NSString *)CFSTR(e)) +- (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error { + [outlineView.window presentError:error + modalForWindow:outlineView.window + delegate:nil + didPresentSelector:nil + contextInfo:nil]; +} -- (void)openHid { - if (_hidManager) - return; - NSLog(@"Opening HID manager."); - _hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); - IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) }, - @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) }, - @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), - NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) } - ]; - IOHIDManagerSetDeviceMatchingMultiple(_hidManager, (__bridge CFArrayRef)criteria); - IOReturn ret = IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone); - if (ret != kIOReturnSuccess) { - [[NSAlert alertWithMessageText:NSLocalizedString(@"input devices unavailable", - @"error title when devices can't be read") - defaultButton:nil - alternateButton:nil - otherButton:nil - informativeTextWithFormat:NSLocalizedString(@"input error 0x%08x occurred", - @"message containing IOReturn failure code when devices can't be read"), ret] - beginSheetModalForWindow:outlineView.window - modalDelegate:nil - didEndSelector:nil - contextInfo:nil]; - [self closeHid]; - } else { - IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, add_callback, (__bridge void *)self); - IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, remove_callback, (__bridge void *)self); - hidSleepingPrompt.hidden = YES; - connectDevicePrompt.hidden = !!_devices.count; - } +- (void)hidManagerDidStart:(NJHIDManager *)manager { + hidSleepingPrompt.hidden = YES; + connectDevicePrompt.hidden = !!_devices.count; } -- (void)closeHid { - if (_hidManager) { - NSLog(@"Closing HID manager."); - IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); - IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone); - CFRelease(_hidManager); - _hidManager = NULL; - } +- (void)hidManagerDidStop:(NJHIDManager *)manager { [_devices removeAllObjects]; [outlineView reloadData]; hidSleepingPrompt.hidden = NO; connectDevicePrompt.hidden = YES; } +- (void)startHid { + [_hidManager start]; +} + +- (void)stopHid { + [_hidManager stop]; +} + - (NJInput *)selectedInput { id item = [outlineView itemAtRow:outlineView.selectedRow]; return (!item.children && item.base) ? item : nil; @@ -337,9 +312,9 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn object:self]; if (!translatingEvents && !NSApplication.sharedApplication.isActive) - [self closeHid]; + [self stopHid]; else if (translatingEvents || NSApplication.sharedApplication.isActive) - [self openHid]; + [self startHid]; } } @@ -355,16 +330,9 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn } } -- (void)closeHidIfDisabled:(NSNotification *)application { +- (void)stopHidIfDisabled:(NSNotification *)application { if (!self.translatingEvents) - [self closeHid]; -} - -- (void)applicationDidFinishLaunching:(NSNotification *)application { - // NSApplicationWillBecomeActiveNotification occurs just slightly - // too late - there's one tick where the UI is showing "No - // devices" even with a device plugged in. - [self openHid]; + [self stopHid]; } - (IBAction)translatingEventsChanged:(NSButton *)sender { diff --git a/Classes/NJHIDManager.h b/Classes/NJHIDManager.h new file mode 100644 index 0000000..752a6e5 --- /dev/null +++ b/Classes/NJHIDManager.h @@ -0,0 +1,45 @@ +// +// NJHIDManager.h +// Enjoyable +// +// Created by Joe Wreschnig on 3/13/13. +// +// + +#import + +@protocol NJHIDManagerDelegate; + +@interface NJHIDManager : NSObject + +@property (nonatomic, copy) NSArray *criteria; + // Changing the criteria may trigger a stop and restart. If this happens, + // messages will be sent to the delegate as usual. + +@property (nonatomic, assign) BOOL running; +@property (nonatomic, weak) id delegate; + +- (id)initWithCriteria:(NSArray *)criteria + delegate:(id )delegate; + +- (void)start; +- (void)stop; + +@end + +@protocol NJHIDManagerDelegate + +- (void)hidManagerDidStart:(NJHIDManager *)manager; +- (void)hidManagerDidStop:(NJHIDManager *)manager; + // Stopping the device will not trigger any removal events, so any + // cleanup in the delegate must be done here. + +- (void)hidManager:(NJHIDManager *)manager didError:(NSError *)error; + +- (void)hidManager:(NJHIDManager *)manager deviceAdded:(IOHIDDeviceRef)device; +- (void)hidManager:(NJHIDManager *)manager deviceRemoved:(IOHIDDeviceRef)device; + +- (void)hidManager:(NJHIDManager *)manager + valueChanged:(IOHIDValueRef)value + fromDevice:(IOHIDDeviceRef)device; +@end diff --git a/Classes/NJHIDManager.m b/Classes/NJHIDManager.m new file mode 100644 index 0000000..2a139df --- /dev/null +++ b/Classes/NJHIDManager.m @@ -0,0 +1,99 @@ +// +// NJHIDManager.m +// Enjoyable +// +// Created by Joe Wreschnig on 3/13/13. +// +// + +#import "NJHIDManager.h" + +@implementation NJHIDManager { + NSArray *_criteria; + IOHIDManagerRef _manager; +} + +- (id)initWithCriteria:(NSArray *)criteria + delegate:(id )delegate +{ + if ((self = [super init])) { + self.criteria = criteria; + self.delegate = delegate; + } + return self; +} + +- (void)dealloc { + [self stop]; +} + +static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) { + NJHIDManager *self = (__bridge NJHIDManager *)ctx; + IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender); + [self.delegate hidManager:self valueChanged:value fromDevice:device]; +} + +static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { + NJHIDManager *self = (__bridge NJHIDManager *)ctx; + [self.delegate hidManager:self deviceAdded:device]; + IOHIDDeviceRegisterInputValueCallback(device, input_callback, ctx); +} + +static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { + NJHIDManager *self = (__bridge NJHIDManager *)ctx; + [self.delegate hidManager:self deviceRemoved:device]; +} + +- (void)start { + if (self.running) + return; + IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatchingMultiple(manager, (__bridge CFArrayRef)self.criteria); + IOReturn ret = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone); + if (ret != kIOReturnSuccess) { + NSError *error = [NSError errorWithDomain:NSMachErrorDomain code:ret userInfo:nil]; + IOHIDManagerClose(manager, kIOHIDOptionsTypeNone); + CFRelease(manager); + [self.delegate hidManager:self didError:error]; + } else { + _manager = manager; + IOHIDManagerScheduleWithRunLoop(_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerRegisterDeviceMatchingCallback(_manager, add_callback, (__bridge void *)self); + IOHIDManagerRegisterDeviceRemovalCallback(_manager, remove_callback, (__bridge void *)self); + [self.delegate hidManagerDidStart:self]; + NSLog(@"Started HID manager."); + } +} + +- (void)stop { + if (!self.running) + return; + IOHIDManagerUnscheduleFromRunLoop(_manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerClose(_manager, kIOHIDOptionsTypeNone); + CFRelease(_manager); + _manager = NULL; + [self.delegate hidManagerDidStop:self]; + NSLog(@"Stopped HID manager."); +} + +- (BOOL)running { + return !!_manager; +} + +- (NSArray *)criteria { + return _criteria; +} + +- (void)setCriteria:(NSArray *)criteria { + if (!criteria) + criteria = @[]; + if (![criteria isEqualToArray:_criteria]) { + BOOL running = !!_manager; + [self stop]; + _criteria = [criteria copy]; + if (running) + [self start]; + } +} + +@end diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index f1bf0ff..ea3ba90 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */; }; EED4CE7716EE195100C65AA8 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EED4CE7616EE195100C65AA8 /* Sparkle.framework */; }; EED4CE7816EE195B00C65AA8 /* Sparkle.framework in Copy Sparkle Framework */ = {isa = PBXBuildFile; fileRef = EED4CE7616EE195100C65AA8 /* Sparkle.framework */; }; + EEE703DC16F089FE002FDD69 /* NJHIDManager.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE703DB16F089FE002FDD69 /* NJHIDManager.m */; }; EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */; }; EEF17D1916E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns in Resources */ = {isa = PBXBuildFile; fileRef = EEF17D1716E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns */; }; EEF17D1F16E8E23A00D7DC4D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = EEF17D1B16E8E23A00D7DC4D /* InfoPlist.strings */; }; @@ -79,6 +80,8 @@ EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJMappingMenuController.h; path = Classes/NJMappingMenuController.h; sourceTree = ""; }; EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJMappingMenuController.m; path = Classes/NJMappingMenuController.m; sourceTree = ""; }; EED4CE7616EE195100C65AA8 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; + EEE703DA16F089FE002FDD69 /* NJHIDManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJHIDManager.h; path = Classes/NJHIDManager.h; sourceTree = ""; }; + EEE703DB16F089FE002FDD69 /* NJHIDManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJHIDManager.m; path = Classes/NJHIDManager.m; sourceTree = ""; }; EEE73B1416EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+NJPossibleNames.h"; path = "Categories/NSRunningApplication+NJPossibleNames.h"; sourceTree = ""; }; EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+NJPossibleNames.m"; path = "Categories/NSRunningApplication+NJPossibleNames.m"; sourceTree = ""; }; EEF17D1716E8E21A00D7DC4D /* com.yukkurigames.Enjoyable.mapping.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = com.yukkurigames.Enjoyable.mapping.icns; path = Resources/com.yukkurigames.Enjoyable.mapping.icns; sourceTree = ""; }; @@ -191,6 +194,8 @@ EEF17D5B16E8E2EF00D7DC4D /* NJOutputMouseScroll.m */, EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */, EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */, + EEE703DA16F089FE002FDD69 /* NJHIDManager.h */, + EEE703DB16F089FE002FDD69 /* NJHIDManager.m */, ); name = Classes; sourceTree = ""; @@ -426,6 +431,7 @@ EEF17D6C16E8E2EF00D7DC4D /* NJOutputMouseScroll.m in Sources */, EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */, EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */, + EEE703DC16F089FE002FDD69 /* NJHIDManager.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Info.plist b/Info.plist index 2add159..2aada49 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 222 + 226 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright diff --git a/Resources/English.lproj/Localizable.strings b/Resources/English.lproj/Localizable.strings index 63e6dc4430ca8fad9105a6a0aef9f3a16e0d5668..564ef88d1ae705702f756a03d3bddd1b4785727c 100644 GIT binary patch delta 12 TcmbPYKEr6k5$??nd?CC5A%_Ha delta 609 zcmb`FF;2rk5Ji7OK};$NC`XnMf+CPKL_(AlsgP)>9VY}N$3%_;xbh)P!7-4y0%zeI zyxBxblp+;cdB^_y=g<5dfA&7EzJ8*{+6Q&1XO+q{mg!AT#GJPg@s*R5IOjW60)7h9 zGz{E=J%MLB*M(Y&Vdt7@5jflV3(gW06IdDhFjS0Xecd1tYp^3Zkry+hH$oR#DhV5Y}aY5#VuR4>8gf0`cGh!+SK6u8v8PT2cI14~(6 z+{=$JY%Alp0o$G98HSlcRk&rhSfXi|a$TR1PpPf|y~y?eJ0P1kJSDLL%i*s_>UXki lFa^y5!v|UNII?VG*{UndSdMkDbImW%Y95ZyzJH@$`wv*@Z#)11 -- 2.20.1