2 // JoystickController.m
5 // Created by Sam McCall on 4/05/09.
8 #import "JoystickController.h"
10 @implementation JoystickController
12 @synthesize joysticks, runningTargets, selectedAction, frontWindowOnly;
15 if(self=[super init]) {
16 joysticks = [[NSMutableArray alloc]init];
17 runningTargets = [[NSMutableArray alloc]init];
18 programmaticallySelecting = NO;
19 mouseLoc.x = mouseLoc.y = 0;
25 for(int i=0; i<[joysticks count]; i++) {
26 [joysticks[i] invalidate];
28 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
29 CFRelease(hidManager);
32 static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
34 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
35 dict[(NSString*)CFSTR(kIOHIDDeviceUsagePageKey)] = @(inUsagePage);
36 dict[(NSString*)CFSTR(kIOHIDDeviceUsageKey)] = @(inUsage);
40 -(void) expandRecursive: (id) handler {
42 [self expandRecursive: [handler base]];
43 [outlineView expandItem: handler];
46 static void timer_callback(CFRunLoopTimerRef timer, void *ctx) {
47 JoystickController *jc = (__bridge JoystickController *)ctx;
48 jc->mouseLoc = [NSEvent mouseLocation];
49 for (Target *target in [jc runningTargets]) {
54 static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) {
55 JoystickController *controller = (__bridge JoystickController *)ctx;
56 IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender);
58 Joystick *js = [controller findJoystickByRef:device];
59 if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
60 JSAction* mainAction = [js actionForEvent: value];
64 [mainAction notifyEvent: value];
65 NSArray* children = [mainAction children];
67 children = @[mainAction];
68 for(id subaction in children) {
69 Target* target = [[controller->configsController currentConfig] getTargetForAction:subaction];
72 /* target application? doesn't seem to be any need since we are only active when it's in front */
73 /* might be required for some strange actions */
74 if ([target running] != [subaction active]) {
75 if ([subaction active]) {
76 [target trigger: controller];
79 [target untrigger: controller];
81 [target setRunning: [subaction active]];
84 if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
85 double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
86 [target setInputValue: realValue];
88 // Add to list of running targets
89 if ([target isContinuous] && [target running]) {
90 if (![controller.runningTargets containsObject:target]) {
91 [[controller runningTargets] addObject: target];
96 } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
97 // joysticks not active, use it to select stuff
98 id handler = [js handlerForEvent: value];
102 [controller expandRecursive: handler];
103 controller->programmaticallySelecting = YES;
104 [controller->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [controller->outlineView rowForItem: handler]] byExtendingSelection: NO];
108 static int findAvailableIndex(id list, Joystick* js) {
111 for(int index=0;;index++) {
113 for(int i=0; i<[list count]; i++) {
115 if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
125 static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
126 JoystickController *controller = (__bridge JoystickController *)ctx;
127 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)controller);
128 Joystick *js = [[Joystick alloc] initWithDevice:device];
129 js.index = findAvailableIndex(controller.joysticks, js);
130 [[controller joysticks] addObject:js];
131 [controller->outlineView reloadData];
134 - (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
135 for (Joystick *js in joysticks)
136 if (js.device == device)
141 static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
142 JoystickController *controller = (__bridge JoystickController *)ctx;
143 Joystick *match = [controller findJoystickByRef:device];
144 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
146 [controller.joysticks removeObject:match];
147 [controller->outlineView reloadData];
152 hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
153 NSArray *criteria = @[
154 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
155 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
156 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController)];
158 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)CFBridgingRetain(criteria));
160 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
161 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
164 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self );
165 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
167 // Setup timer for continuous targets
168 CFRunLoopTimerContext ctx = {
169 0, (__bridge void*)self, NULL, NULL, NULL
171 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
172 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
173 0, 0, timer_callback, &ctx);
174 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
177 - (JSAction *)selectedAction {
178 id item = [outlineView itemAtRow:outlineView.selectedRow];
179 return [item children] ? nil : item;
182 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
183 return item ? [[item children] count] : [joysticks count];
186 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
187 return item ? [[item children] count] > 0: YES;
190 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
191 return item ? [item children][index] : joysticks[index];
194 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
200 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
201 [targetController reset];
202 [targetController load];
203 if (programmaticallySelecting)
204 [targetController focusKey];
205 programmaticallySelecting = NO;