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 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* subactions = [mainAction subActions];
67 subactions = @[mainAction];
68 for(id subaction in subactions) {
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 [js populateActions];
131 [[controller joysticks] addObject:js];
132 [controller->outlineView reloadData];
135 - (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
136 for (Joystick *js in joysticks)
137 if (js.device == device)
142 static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
143 JoystickController *controller = (__bridge JoystickController *)ctx;
144 Joystick *match = [controller findJoystickByRef:device];
145 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
147 [controller.joysticks removeObject:match];
148 [controller->outlineView reloadData];
153 hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
154 NSArray *criteria = @[
155 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
156 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
157 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController)];
159 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)CFBridgingRetain(criteria));
161 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
162 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
165 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self );
166 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
168 // Setup timer for continuous targets
169 CFRunLoopTimerContext ctx = {
170 0, (__bridge void*)self, NULL, NULL, NULL
172 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
173 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
174 0, 0, timer_callback, &ctx);
175 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
178 -(id) determineSelectedAction {
179 id item = [outlineView itemAtRow: [outlineView selectedRow]];
182 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
184 if([item isKindOfClass: [Joystick class]])
191 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
193 return [joysticks count];
194 if([item isKindOfClass: [Joystick class]])
195 return [[item children] count];
196 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
197 return [[item subActions] count];
201 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
204 if([item isKindOfClass: [Joystick class]])
206 if([item isKindOfClass: [JSAction class]])
207 return [item subActions]==NULL ? NO : YES;
211 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
213 return joysticks[index];
215 if([item isKindOfClass: [Joystick class]])
216 return [item children][index];
218 if([item isKindOfClass: [JSAction class]])
219 return [item subActions][index];
223 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
229 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
230 [targetController reset];
231 selectedAction = [self determineSelectedAction];
232 [targetController load];
233 if(programmaticallySelecting)
234 [targetController focusKey];
235 programmaticallySelecting = NO;