Split actual IOKit HID interfacing off from NJDeviceController.
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Wed, 13 Mar 2013 11:23:11 +0000 (12:23 +0100)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Wed, 13 Mar 2013 11:23:11 +0000 (12:23 +0100)
Classes/NJDeviceController.h
Classes/NJDeviceController.m
Classes/NJHIDManager.h [new file with mode: 0644]
Classes/NJHIDManager.m [new file with mode: 0644]
Enjoyable.xcodeproj/project.pbxproj
Info.plist
Resources/English.lproj/Localizable.strings

index 71a0ed17da08d58b56d9558a328f6ee80cc4d772..f49d6472838a491b8252bb3827f58b5720f08250 100644 (file)
@@ -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 <NSOutlineViewDataSource, NSOutlineViewDelegate> {
+@interface NJDeviceController : NSObject <NSOutlineViewDataSource,
+                                          NSOutlineViewDelegate,
+                                          NJHIDManagerDelegate> {
     IBOutlet NSOutlineView *outlineView;
     IBOutlet NJOutputController *outputController;
     IBOutlet NJMappingsController *mappingsController;
index 2a1546c974c8ce7a64b352fe5eec2ecdc8a35d17..610c07b5a4f29c925572a850209c3b4e3d6bfc9d 100644 (file)
@@ -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])) {
             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];
         
         // 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 <NJInputPathElement>)pathElement {
     [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 <NJInputPathElement> 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 (file)
index 0000000..752a6e5
--- /dev/null
@@ -0,0 +1,45 @@
+//
+//  NJHIDManager.h
+//  Enjoyable
+//
+//  Created by Joe Wreschnig on 3/13/13.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@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 <NJHIDManagerDelegate> delegate;
+
+- (id)initWithCriteria:(NSArray *)criteria
+              delegate:(id <NJHIDManagerDelegate>)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 (file)
index 0000000..2a139df
--- /dev/null
@@ -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 <NJHIDManagerDelegate>)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
index f1bf0ff4f8e62ea8b9003a4ef9479ca6294377ca..ea3ba9017b6c81b634ba339081cae2cbb709a7bb 100644 (file)
@@ -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 = "<group>"; };
                EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJMappingMenuController.m; path = Classes/NJMappingMenuController.m; sourceTree = "<group>"; };
                EED4CE7616EE195100C65AA8 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = "<group>"; };
+               EEE703DA16F089FE002FDD69 /* NJHIDManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NJHIDManager.h; path = Classes/NJHIDManager.h; sourceTree = "<group>"; };
+               EEE703DB16F089FE002FDD69 /* NJHIDManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NJHIDManager.m; path = Classes/NJHIDManager.m; sourceTree = "<group>"; };
                EEE73B1416EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSRunningApplication+NJPossibleNames.h"; path = "Categories/NSRunningApplication+NJPossibleNames.h"; sourceTree = "<group>"; };
                EEE73B1516EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSRunningApplication+NJPossibleNames.m"; path = "Categories/NSRunningApplication+NJPossibleNames.m"; sourceTree = "<group>"; };
                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 = "<group>"; };
                                EEF17D5B16E8E2EF00D7DC4D /* NJOutputMouseScroll.m */,
                                EED4CE6C16ED692400C65AA8 /* NJMappingMenuController.h */,
                                EED4CE6D16ED692400C65AA8 /* NJMappingMenuController.m */,
+                               EEE703DA16F089FE002FDD69 /* NJHIDManager.h */,
+                               EEE703DB16F089FE002FDD69 /* NJHIDManager.m */,
                        );
                        name = Classes;
                        sourceTree = "<group>";
                                EEF17D6C16E8E2EF00D7DC4D /* NJOutputMouseScroll.m in Sources */,
                                EEE73B1616EA42E5009D9D99 /* NSRunningApplication+NJPossibleNames.m in Sources */,
                                EED4CE6E16ED692400C65AA8 /* NJMappingMenuController.m in Sources */,
+                               EEE703DC16F089FE002FDD69 /* NJHIDManager.m in Sources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
index 2add15970f208dafbab64429ae2578ee68517a33..2aada49210af30e3fdf4c7c6fdd6cb1839770446 100644 (file)
@@ -46,7 +46,7 @@
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleVersion</key>
-       <string>222</string>
+       <string>226</string>
        <key>LSApplicationCategoryType</key>
        <string>public.app-category.utilities</string>
        <key>NSHumanReadableCopyright</key>
index 63e6dc4430ca8fad9105a6a0aef9f3a16e0d5668..564ef88d1ae705702f756a03d3bddd1b4785727c 100644 (file)
Binary files a/Resources/English.lproj/Localizable.strings and b/Resources/English.lproj/Localizable.strings differ