// Created by Sam McCall on 4/05/09.
//
-#import "CoreFoundation/CoreFoundation.h"
+#import "JoystickController.h"
-@implementation JoystickController
+#import "Config.h"
+#import "ConfigsController.h"
+#import "Joystick.h"
+#import "JSAction.h"
+#import "Target.h"
+#import "TargetController.h"
-@synthesize joysticks, runningTargets, selectedAction, frontWindowOnly;
-
--(id) init {
- if(self=[super init]) {
- joysticks = [[NSMutableArray alloc]init];
- runningTargets = [[NSMutableArray alloc]init];
- programmaticallySelecting = NO;
- mouseLoc.x = mouseLoc.y = 0;
- }
- return self;
+@implementation JoystickController {
+ IOHIDManagerRef hidManager;
+ NSTimer *continuousTimer;
+ NSMutableArray *runningTargets;
}
--(void) finalize {
- for(int i=0; i<[joysticks count]; i++) {
- [joysticks[i] invalidate];
- }
- IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
- CFRelease(hidManager);
- [super finalize];
+- (id)init {
+ if ((self = [super init])) {
+ _joysticks = [[NSMutableArray alloc] initWithCapacity:16];
+ runningTargets = [[NSMutableArray alloc] initWithCapacity:32];
+ }
+ return self;
}
-static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
-{
- NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
- dict[(NSString*)CFSTR(kIOHIDDeviceUsagePageKey)] = [NSNumber numberWithInt: inUsagePage];
- dict[(NSString*)CFSTR(kIOHIDDeviceUsageKey)] = [NSNumber numberWithInt: inUsage];
- return dict;
-}
+- (void)dealloc {
+ [continuousTimer invalidate];
+ IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
+ CFRelease(hidManager);
+}
--(void) expandRecursive: (id) handler {
- if([handler base])
- [self expandRecursive: [handler base]];
- [outlineView expandItem: handler];
+- (void)expandRecursive:(id)handler {
+ if ([handler base])
+ [self expandRecursive:[handler base]];
+ [outlineView expandItem:handler];
}
-BOOL objInArray(NSMutableArray *array, id object) {
- for (id o in array) {
- if (o == object)
- return true;
+- (void)addRunningTarget:(Target *)target {
+ if (![runningTargets containsObject:target]) {
+ [runningTargets addObject:target];
+ }
+ if (!continuousTimer) {
+ continuousTimer = [NSTimer scheduledTimerWithTimeInterval:1.f/60.f
+ target:self
+ selector:@selector(updateContinuousActions:)
+ userInfo:nil
+ repeats:YES];
+ NSLog(@"Scheduled continuous target timer.");
}
- return false;
}
-void timer_callback(CFRunLoopTimerRef timer, void *ctx) {
- JoystickController *jc = (JoystickController *)ctx;
- jc->mouseLoc = [NSEvent mouseLocation];
- for (Target *target in [jc runningTargets]) {
- [target update: jc];
+static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) {
+ JoystickController *controller = (__bridge JoystickController *)ctx;
+ IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender);
+
+ Joystick *js = [controller findJoystickByRef:device];
+ if (controller.sendingRealEvents) {
+ JSAction *mainAction = [js actionForEvent:value];
+ [mainAction notifyEvent:value];
+ NSArray *children = mainAction.children ? mainAction.children : mainAction ? @[mainAction] : @[];
+ for (JSAction *subaction in children) {
+ Target *target = controller.currentConfig[subaction];
+ target.magnitude = mainAction.magnitude;
+ target.running = subaction.active;
+ if (target.running && target.isContinuous)
+ [controller addRunningTarget:target];
+ }
+ } else if ([NSApplication sharedApplication].isActive
+ && [NSApplication sharedApplication].mainWindow.isVisible) {
+ JSAction *handler = [js handlerForEvent:value];
+ if (!handler)
+ return;
+
+ [controller expandRecursive:handler];
+ [controller->outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[controller->outlineView rowForItem:handler]] byExtendingSelection: NO];
+ [controller->targetController focusKey];
}
}
-void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
- JoystickController* self = (JoystickController*)inContext;
- IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
-
- Joystick* js = [self findJoystickByRef: device];
- if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
- // for reals
- JSAction* mainAction = [js actionForEvent: value];
- if(!mainAction)
- return;
-
- [mainAction notifyEvent: value];
- NSArray* subactions = [mainAction subActions];
- if(!subactions)
- subactions = @[mainAction];
- for(id subaction in subactions) {
- Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
- if(!target)
- continue;
- /* target application? doesn't seem to be any need since we are only active when it's in front */
- /* might be required for some strange actions */
- if ([target running] != [subaction active]) {
- if ([subaction active]) {
- [target trigger: self];
- }
- else {
- [target untrigger: self];
- }
- [target setRunning: [subaction active]];
- }
-
- if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
- double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
- [target setInputValue: realValue];
-
- // Add to list of running targets
- if ([target isContinuous] && [target running]) {
- if (!objInArray([self runningTargets], target)) {
- [[self runningTargets] addObject: target];
- }
- }
+static int findAvailableIndex(NSArray *list, Joystick *js) {
+ for (int index = 1; ; index++) {
+ BOOL available = YES;
+ for (Joystick *used in list) {
+ if ([used.productName isEqualToString:js.productName] && used.index == index) {
+ available = NO;
+ break;
}
- }
- } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
- // joysticks not active, use it to select stuff
- id handler = [js handlerForEvent: value];
- if(!handler)
- return;
-
- [self expandRecursive: handler];
- self->programmaticallySelecting = YES;
- [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
- }
+ }
+ if (available)
+ return index;
+ }
}
-int findAvailableIndex(id list, Joystick* js) {
- BOOL available;
- Joystick* js2;
- for(int index=0;;index++) {
- available = YES;
- for(int i=0; i<[list count]; i++) {
- js2 = list[i];
- if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
- available = NO;
- break;
- }
- }
- if(available)
- return index;
- }
+static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
+ JoystickController *controller = (__bridge JoystickController *)ctx;
+ IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)controller);
+ Joystick *js = [[Joystick alloc] initWithDevice:device];
+ js.index = findAvailableIndex(controller.joysticks, js);
+ [[controller joysticks] addObject:js];
+ [controller->outlineView reloadData];
}
-void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
- JoystickController* self = (JoystickController*)inContext;
-
- IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
- IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) self);
-
- Joystick *js = [[Joystick alloc] initWithDevice: device];
- [js setIndex: findAvailableIndex([self joysticks], js)];
-
- [js populateActions];
-
- [[self joysticks] addObject: js];
- [self->outlineView reloadData];
-}
-
--(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
- for (Joystick *js in joysticks)
+- (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
+ for (Joystick *js in _joysticks)
if (js.device == device)
return js;
- return nil;
-}
+ return nil;
+}
-void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
- JoystickController* self = (JoystickController*)inContext;
-
- Joystick* match = [self findJoystickByRef: device];
- if(!match)
- return;
-
- [[self joysticks] removeObject: match];
+static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
+ JoystickController *controller = (__bridge JoystickController *)ctx;
+ Joystick *match = [controller findJoystickByRef:device];
+ IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
+ if (match) {
+ [controller.joysticks removeObject:match];
+ [controller->outlineView reloadData];
+ }
+}
- [match invalidate];
- [self->outlineView reloadData];
+- (void)updateContinuousActions:(NSTimer *)timer {
+ self.mouseLoc = [NSEvent mouseLocation];
+ for (Target *target in [runningTargets copy]) {
+ if (![target update:self]) {
+ [runningTargets removeObject:target];
+ }
+ }
+ if (!runningTargets.count) {
+ [continuousTimer invalidate];
+ continuousTimer = nil;
+ NSLog(@"Unscheduled continuous target timer.");
+ }
}
--(void) setup {
- hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
- NSArray *criteria = @[create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
- create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
- create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController)];
-
- IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria);
-
- IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
- IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
- (void)tIOReturn;
-
- IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (void*)self );
- IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (void*) self);
-// IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self);
-// register individually so we can find the device more easily
+#define NSSTR(e) ((NSString *)CFSTR(e))
+
+- (void)setup {
+ hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
+ 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);
+ IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
+ IOReturn ret = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
+ if (ret != kIOReturnSuccess) {
+ [[NSAlert alertWithMessageText:@"Input devices are unavailable"
+ defaultButton:nil
+ alternateButton:nil
+ otherButton:nil
+ informativeTextWithFormat:@"Error 0x%08x occured trying to access your devices. "
+ @"Input may not be correctly detected or mapped.",
+ ret]
+ runModal];
+ }
-
- // Setup timer for continuous targets
- CFRunLoopTimerContext ctx = {
- 0, (void*)self, NULL, NULL, NULL
- };
- CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
- CFAbsoluteTimeGetCurrent(), 1.0/80.0,
- 0, 0, timer_callback, &ctx);
- CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
+ IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self);
+ IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
}
--(id) determineSelectedAction {
- id item = [outlineView itemAtRow: [outlineView selectedRow]];
- if(!item)
- return NULL;
- if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
- return NULL;
- if([item isKindOfClass: [Joystick class]])
- return NULL;
- return item;
+- (Config *)currentConfig {
+ return configsController.currentConfig;
}
-/* outline view */
+- (JSAction *)selectedAction {
+ id item = [outlineView itemAtRow:outlineView.selectedRow];
+ return [item children] ? nil : item;
+}
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
- if(item == nil)
- return [joysticks count];
- if([item isKindOfClass: [Joystick class]])
- return [[item children] count];
- if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
- return [[item subActions] count];
- return 0;
+ return item ? [[item children] count] : _joysticks.count;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
- if(item == nil)
- return YES;
- if([item isKindOfClass: [Joystick class]])
- return YES;
- if([item isKindOfClass: [JSAction class]])
- return [item subActions]==NULL ? NO : YES;
- return NO;
+ return item ? [[item children] count] > 0: YES;
}
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
- if(item == nil)
- return joysticks[index];
-
- if([item isKindOfClass: [Joystick class]])
- return [item children][index];
-
- if([item isKindOfClass: [JSAction class]])
- return [item subActions][index];
-
- return NULL;
+ return item ? [item children][index] : _joysticks[index];
}
+
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
- if(item == nil)
- return @"root";
- return [item name];
+ if(item == nil)
+ return @"root";
+ return [item name];
}
-- (void)outlineViewSelectionDidChange: (NSNotification*) notification {
- [targetController reset];
- selectedAction = [self determineSelectedAction];
- [targetController load];
- if(programmaticallySelecting)
- [targetController focusKey];
- programmaticallySelecting = NO;
+- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
+ [targetController loadCurrent];
}
-
+
@end