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);
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 BOOL objInArray(NSMutableArray *array, id object) {
54 void timer_callback(CFRunLoopTimerRef timer, void *ctx) {
55 JoystickController *jc = (__bridge JoystickController *)ctx;
56 jc->mouseLoc = [NSEvent mouseLocation];
57 for (Target *target in [jc runningTargets]) {
62 void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
63 JoystickController* self = (__bridge JoystickController*)inContext;
64 IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
66 Joystick* js = [self findJoystickByRef: device];
67 if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
69 JSAction* mainAction = [js actionForEvent: value];
73 [mainAction notifyEvent: value];
74 NSArray* subactions = [mainAction subActions];
76 subactions = @[mainAction];
77 for(id subaction in subactions) {
78 Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
81 /* target application? doesn't seem to be any need since we are only active when it's in front */
82 /* might be required for some strange actions */
83 if ([target running] != [subaction active]) {
84 if ([subaction active]) {
85 [target trigger: self];
88 [target untrigger: self];
90 [target setRunning: [subaction active]];
93 if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
94 double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
95 [target setInputValue: realValue];
97 // Add to list of running targets
98 if ([target isContinuous] && [target running]) {
99 if (!objInArray([self runningTargets], target)) {
100 [[self runningTargets] addObject: target];
105 } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
106 // joysticks not active, use it to select stuff
107 id handler = [js handlerForEvent: value];
111 [self expandRecursive: handler];
112 self->programmaticallySelecting = YES;
113 [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
117 int findAvailableIndex(id list, Joystick* js) {
120 for(int index=0;;index++) {
122 for(int i=0; i<[list count]; i++) {
124 if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
134 void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
135 JoystickController* self = (__bridge JoystickController*)inContext;
137 IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
138 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) CFBridgingRetain(self));
140 Joystick *js = [[Joystick alloc] initWithDevice: device];
141 [js setIndex: findAvailableIndex([self joysticks], js)];
143 [js populateActions];
145 [[self joysticks] addObject: js];
146 [self->outlineView reloadData];
149 -(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
150 for (Joystick *js in joysticks)
151 if (js.device == device)
156 void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
157 JoystickController* self = CFBridgingRelease(inContext);
159 Joystick* match = [self findJoystickByRef: device];
163 [[self joysticks] removeObject: match];
166 [self->outlineView reloadData];
170 hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
171 NSArray *criteria = @[create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
172 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
173 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController)];
175 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)CFBridgingRetain(criteria));
177 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
178 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
181 IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (__bridge void*)self );
182 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void*) self);
183 // IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self);
184 // register individually so we can find the device more easily
188 // Setup timer for continuous targets
189 CFRunLoopTimerContext ctx = {
190 0, (__bridge void*)self, NULL, NULL, NULL
192 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
193 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
194 0, 0, timer_callback, &ctx);
195 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
198 -(id) determineSelectedAction {
199 id item = [outlineView itemAtRow: [outlineView selectedRow]];
202 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
204 if([item isKindOfClass: [Joystick class]])
211 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
213 return [joysticks count];
214 if([item isKindOfClass: [Joystick class]])
215 return [[item children] count];
216 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
217 return [[item subActions] count];
221 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
224 if([item isKindOfClass: [Joystick class]])
226 if([item isKindOfClass: [JSAction class]])
227 return [item subActions]==NULL ? NO : YES;
231 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
233 return joysticks[index];
235 if([item isKindOfClass: [Joystick class]])
236 return [item children][index];
238 if([item isKindOfClass: [JSAction class]])
239 return [item subActions][index];
243 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
249 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
250 [targetController reset];
251 selectedAction = [self determineSelectedAction];
252 [targetController load];
253 if(programmaticallySelecting)
254 [targetController focusKey];
255 programmaticallySelecting = NO;