X-Git-Url: https://git.yukkurigames.com/?p=enjoyable.git;a=blobdiff_plain;f=JoystickController.m;h=33201f5ba2b98e6235f6f5506c2fc129591785fd;hp=976b531a01c89c61081fa84b2e97b4c6d94a8de3;hb=1bb3f553caac2b6b74380bd20ddc4bb1ff22abb2;hpb=61f8cdec21ab083b29c22aa11fda54d6005666ca diff --git a/JoystickController.m b/JoystickController.m index 976b531..33201f5 100644 --- a/JoystickController.m +++ b/JoystickController.m @@ -5,258 +5,210 @@ // Created by Sam McCall on 4/05/09. // -#import "CoreFoundation/CoreFoundation.h" - -@implementation JoystickController +#import "JoystickController.h" + +#import "Config.h" +#import "ConfigsController.h" +#import "Joystick.h" +#import "JSAction.h" +#import "Target.h" +#import "TargetController.h" + +@implementation JoystickController { + IOHIDManagerRef hidManager; + NSTimer *continuousTimer; + NSMutableArray *runningTargets; + NSMutableArray *_joysticks; +} + +- (id)init { + if ((self = [super init])) { + _joysticks = [[NSMutableArray alloc] initWithCapacity:16]; + runningTargets = [[NSMutableArray alloc] initWithCapacity:32]; + } + return self; +} -@synthesize joysticks, runningTargets, selectedAction, frontWindowOnly; +- (void)dealloc { + [continuousTimer invalidate]; + IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone); + CFRelease(hidManager); +} --(id) init { - if(self=[super init]) { - joysticks = [[NSMutableArray alloc]init]; - runningTargets = [[NSMutableArray alloc]init]; - programmaticallySelecting = NO; - mouseLoc.x = mouseLoc.y = 0; - } - return self; +- (void)expandRecursive:(id)handler { + if ([handler base]) + [self expandRecursive:[handler base]]; + [outlineView expandItem:handler]; } --(void) finalize { - for(int i=0; i<[joysticks count]; i++) { - [[joysticks objectAtIndex:i] invalidate]; - } - IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone); - CFRelease(hidManager); - [super finalize]; +- (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."); + } } -static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage ) -{ - NSMutableDictionary* dict = [[NSMutableDictionary alloc] init]; - [dict setObject: [NSNumber numberWithInt: inUsagePage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)]; - [dict setObject: [NSNumber numberWithInt: inUsage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)]; - return dict; -} +- (void)runTargetForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value { + Joystick *js = [self findJoystickByRef:device]; + JSAction *mainAction = [js actionForEvent:value]; + [mainAction notifyEvent:value]; + NSArray *children = mainAction.children ? mainAction.children : mainAction ? @[mainAction] : @[]; + for (JSAction *subaction in children) { + Target *target = configsController.currentConfig[subaction]; + target.magnitude = mainAction.magnitude; + target.running = subaction.active; + if (target.running && target.isContinuous) + [self addRunningTarget:target]; + } +} --(void) expandRecursive: (id) handler { - if([handler base]) - [self expandRecursive: [handler base]]; - [outlineView expandItem: handler]; +- (void)showTargetForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value { + Joystick *js = [self findJoystickByRef:device]; + JSAction *handler = [js handlerForEvent:value]; + if (!handler) + return; + + [self expandRecursive:handler]; + [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO]; + [targetController focusKey]; } -BOOL objInArray(NSMutableArray *array, id object) { - for (id o in array) { - if (o == object) - return true; +static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) { + JoystickController *controller = (__bridge JoystickController *)ctx; + IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender); + + if (controller.sendingRealEvents) { + [controller runTargetForDevice:device value:value]; + } else if ([NSApplication sharedApplication].mainWindow.isVisible) { + [controller showTargetForDevice:device value:value]; } - 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 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; + } + } + if (available) + return index; } } -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([[[NSApplication sharedApplication] delegate] active]) { - // for reals - JSAction* mainAction = [js actionForEvent: value]; - if(!mainAction) - return; - - [mainAction notifyEvent: value]; - NSArray* subactions = [mainAction subActions]; - if(!subactions) - subactions = [NSArray arrayWithObject: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]; - } - } - } - } - } 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]; - } +- (void)addJoystickForDevice:(IOHIDDeviceRef)device { + IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)self); + Joystick *js = [[Joystick alloc] initWithDevice:device]; + js.index = findAvailableIndex(_joysticks, js); + [_joysticks addObject:js]; + [outlineView reloadData]; } -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 objectAtIndex: 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; + [controller addJoystickForDevice:device]; } -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]; +- (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device { + for (Joystick *js in _joysticks) + if (js.device == device) + return js; + return nil; +} - [[self joysticks] addObject: js]; - [self->outlineView reloadData]; +static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) { + JoystickController *controller = (__bridge JoystickController *)ctx; + [controller removeJoystickForDevice:device]; } - --(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device { - for(int i=0; i<[joysticks count]; i++) - if([[joysticks objectAtIndex:i] device] == device) - return [joysticks objectAtIndex:i]; - return NULL; -} -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]; +- (void)removeJoystickForDevice:(IOHIDDeviceRef)device { + Joystick *match = [self findJoystickByRef:device]; + IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL); + if (match) { + [_joysticks removeObject:match]; + [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 = [NSArray arrayWithObjects: - create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick), - create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad), - create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController), - //create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard), - nil]; - - 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; +- (JSAction *)selectedAction { + id item = [outlineView itemAtRow:outlineView.selectedRow]; + return [item children] ? nil : item; } -/* outline view */ - -- (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; +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item { + 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 objectAtIndex: index]; - - if([item isKindOfClass: [Joystick class]]) - return [[item children] objectAtIndex: index]; - - if([item isKindOfClass: [JSAction class]]) - return [[item subActions] objectAtIndex:index]; - - return NULL; +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item { + 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