Switch controller/HID bridging to non-retained since the controller lives as long...
[enjoyable.git] / JoystickController.m
index b510848..51c2a37 100644 (file)
@@ -5,32 +5,35 @@
 //  Created by Sam McCall on 4/05/09.
 //
 
+#import "CoreFoundation/CoreFoundation.h"
+
 @implementation JoystickController
 
-@synthesize joysticks, selectedAction;
+@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;
 }
 
--(void) finalize {
+-(void) dealloc {
        for(int i=0; i<[joysticks count]; i++) {
-               [[joysticks objectAtIndex:i] invalidate];
+               [joysticks[i] invalidate];
        }
        IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
        CFRelease(hidManager);
-       [super finalize];
 }
 
 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)];
+       dict[(NSString*)CFSTR(kIOHIDDeviceUsagePageKey)] = @(inUsagePage);
+       dict[(NSString*)CFSTR(kIOHIDDeviceUsageKey)] = @(inUsage);
        return dict;
 } 
 
@@ -40,13 +43,20 @@ static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage
        [outlineView expandItem: handler];
 }
 
-void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
-       JoystickController* self = (JoystickController*)inContext;
-       IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
+static void timer_callback(CFRunLoopTimerRef timer, void *ctx) {
+    JoystickController *jc = (__bridge 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 = [self findJoystickByRef: device];
-       if([[[NSApplication sharedApplication] delegate] active]) {
-               // for reals
+       Joystick *js = [controller findJoystickByRef:device];
+       if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
                JSAction* mainAction = [js actionForEvent: value];
                if(!mainAction)
                        return;
@@ -54,15 +64,34 @@ void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDVal
                [mainAction notifyEvent: value];
                NSArray* subactions = [mainAction subActions];
                if(!subactions)
-                       subactions = [NSArray arrayWithObject:mainAction];
+                       subactions = @[mainAction];
                for(id subaction in subactions) {
-                       Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
+                       Target* target = [[controller->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 */
-                       [target setRunning: [subaction active]];
-            [target setInputValue: IOHIDValueGetIntegerValue(value)];
+            if ([target running] != [subaction active]) {
+                if ([subaction active]) {
+                    [target trigger: controller];
+                }
+                else {
+                    [target untrigger: controller];
+                }
+                [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 (![controller.runningTargets containsObject:target]) {
+                        [[controller runningTargets] addObject: target];
+                    }
+                }
+            }
                }
        } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
                // joysticks not active, use it to select stuff
@@ -70,19 +99,19 @@ void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDVal
                if(!handler)
                        return;
        
-               [self expandRecursive: handler];
-               self->programmaticallySelecting = YES;
-               [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
+               [controller expandRecursive: handler];
+               controller->programmaticallySelecting = YES;
+               [controller->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [controller->outlineView rowForItem: handler]] byExtendingSelection: NO];
        }
 }
 
-int findAvailableIndex(id list, Joystick* js) {
+static 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];
+                       js2 = list[i];
                        if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
                                available = NO;
                                break;
@@ -93,60 +122,57 @@ int findAvailableIndex(id list, Joystick* js) {
        }
 }
 
-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)];
-       
+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);
        [js populateActions];
-
-       [[self joysticks] addObject: js];
-       [self->outlineView reloadData];
+       [[controller joysticks] addObject:js];
+       [controller->outlineView reloadData];
 }
        
--(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
-       for(int i=0; i<[joysticks count]; i++)
-               if([[joysticks objectAtIndex:i] device] == device)
-                       return [joysticks objectAtIndex:i];
-       return NULL;
+- (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
+    for (Joystick *js in joysticks)
+        if (js.device == device)
+            return js;
+       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];
-
-       [match invalidate];
-       [self->outlineView reloadData];
+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];
+    }
 }
 
--(void) setup {
+- (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);
+       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)CFBridgingRetain(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
+       IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self );
+       IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
+
+    // Setup timer for continuous targets
+    CFRunLoopTimerContext ctx = {
+        0, (__bridge void*)self, NULL, NULL, NULL
+    };
+    CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
+                                                   CFAbsoluteTimeGetCurrent(), 1.0/80.0,
+                                                   0, 0, timer_callback, &ctx);
+    CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
 }
 
 -(id) determineSelectedAction {
@@ -184,13 +210,13 @@ void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDe
 
 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
        if(item == nil) 
-               return [joysticks objectAtIndex: index];
+               return joysticks[index];
 
        if([item isKindOfClass: [Joystick class]])
-               return [[item children] objectAtIndex: index];
+               return [item children][index];
        
        if([item isKindOfClass: [JSAction class]]) 
-               return [[item subActions] objectAtIndex:index];
+               return [item subActions][index];
 
        return NULL;
 }