#import "NJEvents.h"
@implementation NJDeviceController {
- IOHIDManagerRef _hidManager;
+ NJHIDManager *_hidManager;
NSTimer *_continuousOutputsTick;
NSMutableArray *_continousOutputs;
NSMutableArray *_devices;
}
#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];
}
- (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];
}
}
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;
}
}
}
-- (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)
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) {
}
}
-#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;
object:self];
if (!translatingEvents && !NSApplication.sharedApplication.isActive)
- [self closeHid];
+ [self stopHid];
else if (translatingEvents || NSApplication.sharedApplication.isActive)
- [self openHid];
+ [self startHid];
}
}
}
}
-- (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 {
--- /dev/null
+//
+// 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
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 */; };
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;
};