2 // JoystickController.m
5 // Created by Sam McCall on 4/05/09.
8 #import "CoreFoundation/CoreFoundation.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);
33 static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
35 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
36 dict[(NSString*)CFSTR(kIOHIDDeviceUsagePageKey)] = [NSNumber numberWithInt: inUsagePage];
37 dict[(NSString*)CFSTR(kIOHIDDeviceUsageKey)] = [NSNumber numberWithInt: inUsage];
41 -(void) expandRecursive: (id) handler {
43 [self expandRecursive: [handler base]];
44 [outlineView expandItem: handler];
47 BOOL objInArray(NSMutableArray *array, id object) {
55 void timer_callback(CFRunLoopTimerRef timer, void *ctx) {
56 JoystickController *jc = (JoystickController *)ctx;
57 jc->mouseLoc = [NSEvent mouseLocation];
58 for (Target *target in [jc runningTargets]) {
63 void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
64 JoystickController* self = (JoystickController*)inContext;
65 IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
67 Joystick* js = [self findJoystickByRef: device];
68 if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
70 JSAction* mainAction = [js actionForEvent: value];
74 [mainAction notifyEvent: value];
75 NSArray* subactions = [mainAction subActions];
77 subactions = @[mainAction];
78 for(id subaction in subactions) {
79 Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
82 /* target application? doesn't seem to be any need since we are only active when it's in front */
83 /* might be required for some strange actions */
84 if ([target running] != [subaction active]) {
85 if ([subaction active]) {
86 [target trigger: self];
89 [target untrigger: self];
91 [target setRunning: [subaction active]];
94 if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
95 double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
96 [target setInputValue: realValue];
98 // Add to list of running targets
99 if ([target isContinuous] && [target running]) {
100 if (!objInArray([self runningTargets], target)) {
101 [[self runningTargets] addObject: target];
106 } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
107 // joysticks not active, use it to select stuff
108 id handler = [js handlerForEvent: value];
112 [self expandRecursive: handler];
113 self->programmaticallySelecting = YES;
114 [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
118 int findAvailableIndex(id list, Joystick* js) {
121 for(int index=0;;index++) {
123 for(int i=0; i<[list count]; i++) {
125 if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
135 void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
136 JoystickController* self = (JoystickController*)inContext;
138 IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
139 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) self);
141 Joystick *js = [[Joystick alloc] initWithDevice: device];
142 [js setIndex: findAvailableIndex([self joysticks], js)];
144 [js populateActions];
146 [[self joysticks] addObject: js];
147 [self->outlineView reloadData];
150 -(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
151 for (Joystick *js in joysticks)
152 if (js.device == device)
157 void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
158 JoystickController* self = (JoystickController*)inContext;
160 Joystick* match = [self findJoystickByRef: device];
164 [[self joysticks] removeObject: match];
167 [self->outlineView reloadData];
171 hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
172 NSArray *criteria = @[create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
173 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
174 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController)];
176 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria);
178 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
179 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
182 IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (void*)self );
183 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (void*) self);
184 // IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self);
185 // register individually so we can find the device more easily
189 // Setup timer for continuous targets
190 CFRunLoopTimerContext ctx = {
191 0, (void*)self, NULL, NULL, NULL
193 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
194 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
195 0, 0, timer_callback, &ctx);
196 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
199 -(id) determineSelectedAction {
200 id item = [outlineView itemAtRow: [outlineView selectedRow]];
203 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
205 if([item isKindOfClass: [Joystick class]])
212 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
214 return [joysticks count];
215 if([item isKindOfClass: [Joystick class]])
216 return [[item children] count];
217 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
218 return [[item subActions] count];
222 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
225 if([item isKindOfClass: [Joystick class]])
227 if([item isKindOfClass: [JSAction class]])
228 return [item subActions]==NULL ? NO : YES;
232 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
234 return joysticks[index];
236 if([item isKindOfClass: [Joystick class]])
237 return [item children][index];
239 if([item isKindOfClass: [JSAction class]])
240 return [item subActions][index];
244 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
250 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
251 [targetController reset];
252 selectedAction = [self determineSelectedAction];
253 [targetController load];
254 if(programmaticallySelecting)
255 [targetController focusKey];
256 programmaticallySelecting = NO;