Type references to the application delegate to prevent warnings.
[enjoyable.git] / JoystickController.m
1 //
2 // JoystickController.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "CoreFoundation/CoreFoundation.h"
9
10 @implementation JoystickController
11
12 @synthesize joysticks, runningTargets, selectedAction, frontWindowOnly;
13
14 -(id) init {
15 if(self=[super init]) {
16 joysticks = [[NSMutableArray alloc]init];
17 runningTargets = [[NSMutableArray alloc]init];
18 programmaticallySelecting = NO;
19 mouseLoc.x = mouseLoc.y = 0;
20 }
21 return self;
22 }
23
24 -(void) finalize {
25 for(int i=0; i<[joysticks count]; i++) {
26 [[joysticks objectAtIndex:i] invalidate];
27 }
28 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
29 CFRelease(hidManager);
30 [super finalize];
31 }
32
33 static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
34 {
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)];
38 return dict;
39 }
40
41 -(void) expandRecursive: (id) handler {
42 if([handler base])
43 [self expandRecursive: [handler base]];
44 [outlineView expandItem: handler];
45 }
46
47 BOOL objInArray(NSMutableArray *array, id object) {
48 for (id o in array) {
49 if (o == object)
50 return true;
51 }
52 return false;
53 }
54
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]) {
59 [target update: jc];
60 }
61 }
62
63 void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
64 JoystickController* self = (JoystickController*)inContext;
65 IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
66
67 Joystick* js = [self findJoystickByRef: device];
68 if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
69 // for reals
70 JSAction* mainAction = [js actionForEvent: value];
71 if(!mainAction)
72 return;
73
74 [mainAction notifyEvent: value];
75 NSArray* subactions = [mainAction subActions];
76 if(!subactions)
77 subactions = [NSArray arrayWithObject:mainAction];
78 for(id subaction in subactions) {
79 Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
80 if(!target)
81 continue;
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];
87 }
88 else {
89 [target untrigger: self];
90 }
91 [target setRunning: [subaction active]];
92 }
93
94 if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
95 double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
96 [target setInputValue: realValue];
97
98 // Add to list of running targets
99 if ([target isContinuous] && [target running]) {
100 if (!objInArray([self runningTargets], target)) {
101 [[self runningTargets] addObject: target];
102 }
103 }
104 }
105 }
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];
109 if(!handler)
110 return;
111
112 [self expandRecursive: handler];
113 self->programmaticallySelecting = YES;
114 [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
115 }
116 }
117
118 int findAvailableIndex(id list, Joystick* js) {
119 BOOL available;
120 Joystick* js2;
121 for(int index=0;;index++) {
122 available = YES;
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) {
126 available = NO;
127 break;
128 }
129 }
130 if(available)
131 return index;
132 }
133 }
134
135 void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
136 JoystickController* self = (JoystickController*)inContext;
137
138 IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
139 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) self);
140
141 Joystick *js = [[Joystick alloc] initWithDevice: device];
142 [js setIndex: findAvailableIndex([self joysticks], js)];
143
144 [js populateActions];
145
146 [[self joysticks] addObject: js];
147 [self->outlineView reloadData];
148 }
149
150 -(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
151 for (Joystick *js in joysticks)
152 if (js.device == device)
153 return js;
154 return nil;
155 }
156
157 void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
158 JoystickController* self = (JoystickController*)inContext;
159
160 Joystick* match = [self findJoystickByRef: device];
161 if(!match)
162 return;
163
164 [[self joysticks] removeObject: match];
165
166 [match invalidate];
167 [self->outlineView reloadData];
168 }
169
170 -(void) setup {
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),
177 nil];
178
179 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria);
180
181 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
182 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
183 (void)tIOReturn;
184
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
189
190
191
192 // Setup timer for continuous targets
193 CFRunLoopTimerContext ctx = {
194 0, (void*)self, NULL, NULL, NULL
195 };
196 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
197 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
198 0, 0, timer_callback, &ctx);
199 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
200 }
201
202 -(id) determineSelectedAction {
203 id item = [outlineView itemAtRow: [outlineView selectedRow]];
204 if(!item)
205 return NULL;
206 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
207 return NULL;
208 if([item isKindOfClass: [Joystick class]])
209 return NULL;
210 return item;
211 }
212
213 /* outline view */
214
215 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
216 if(item == nil)
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];
222 return 0;
223 }
224
225 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
226 if(item == nil)
227 return YES;
228 if([item isKindOfClass: [Joystick class]])
229 return YES;
230 if([item isKindOfClass: [JSAction class]])
231 return [item subActions]==NULL ? NO : YES;
232 return NO;
233 }
234
235 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
236 if(item == nil)
237 return [joysticks objectAtIndex: index];
238
239 if([item isKindOfClass: [Joystick class]])
240 return [[item children] objectAtIndex: index];
241
242 if([item isKindOfClass: [JSAction class]])
243 return [[item subActions] objectAtIndex:index];
244
245 return NULL;
246 }
247 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
248 if(item == nil)
249 return @"root";
250 return [item name];
251 }
252
253 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
254 [targetController reset];
255 selectedAction = [self determineSelectedAction];
256 [targetController load];
257 if(programmaticallySelecting)
258 [targetController focusKey];
259 programmaticallySelecting = NO;
260 }
261
262 @end