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 objectAtIndex: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 setObject: [NSNumber numberWithInt: inUsagePage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)];
37 [dict setObject: [NSNumber numberWithInt: inUsage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)];
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([[[NSApplication sharedApplication] delegate] active]) {
70 JSAction* mainAction = [js actionForEvent: value];
74 [mainAction notifyEvent: value];
75 NSArray* subactions = [mainAction subActions];
77 subactions = [NSArray arrayWithObject: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++) {
124 js2 = [list objectAtIndex: 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 = [NSArray arrayWithObjects:
173 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
174 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
175 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
176 //create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
179 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria);
181 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
182 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
185 IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (void*)self );
186 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (void*) self);
187 // IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self);
188 // register individually so we can find the device more easily
192 // Setup timer for continuous targets
193 CFRunLoopTimerContext ctx = {
194 0, (void*)self, NULL, NULL, NULL
196 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
197 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
198 0, 0, timer_callback, &ctx);
199 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
202 -(id) determineSelectedAction {
203 id item = [outlineView itemAtRow: [outlineView selectedRow]];
206 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
208 if([item isKindOfClass: [Joystick class]])
215 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
217 return [joysticks count];
218 if([item isKindOfClass: [Joystick class]])
219 return [[item children] count];
220 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
221 return [[item subActions] count];
225 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
228 if([item isKindOfClass: [Joystick class]])
230 if([item isKindOfClass: [JSAction class]])
231 return [item subActions]==NULL ? NO : YES;
235 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
237 return [joysticks objectAtIndex: index];
239 if([item isKindOfClass: [Joystick class]])
240 return [[item children] objectAtIndex: index];
242 if([item isKindOfClass: [JSAction class]])
243 return [[item subActions] objectAtIndex:index];
247 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
253 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
254 [targetController reset];
255 selectedAction = [self determineSelectedAction];
256 [targetController load];
257 if(programmaticallySelecting)
258 [targetController focusKey];
259 programmaticallySelecting = NO;