Update to ARC (probably introducing some leaks, probably fixing others).
[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) dealloc {
25 for(int i=0; i<[joysticks count]; i++) {
26 [joysticks[i] invalidate];
27 }
28 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
29 CFRelease(hidManager);
30 }
31
32 static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
33 {
34 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
35 dict[(NSString*)CFSTR(kIOHIDDeviceUsagePageKey)] = @(inUsagePage);
36 dict[(NSString*)CFSTR(kIOHIDDeviceUsageKey)] = @(inUsage);
37 return dict;
38 }
39
40 -(void) expandRecursive: (id) handler {
41 if([handler base])
42 [self expandRecursive: [handler base]];
43 [outlineView expandItem: handler];
44 }
45
46 BOOL objInArray(NSMutableArray *array, id object) {
47 for (id o in array) {
48 if (o == object)
49 return true;
50 }
51 return false;
52 }
53
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]) {
58 [target update: jc];
59 }
60 }
61
62 void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
63 JoystickController* self = (__bridge JoystickController*)inContext;
64 IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
65
66 Joystick* js = [self findJoystickByRef: device];
67 if([(ApplicationController *)[[NSApplication sharedApplication] delegate] active]) {
68 // for reals
69 JSAction* mainAction = [js actionForEvent: value];
70 if(!mainAction)
71 return;
72
73 [mainAction notifyEvent: value];
74 NSArray* subactions = [mainAction subActions];
75 if(!subactions)
76 subactions = @[mainAction];
77 for(id subaction in subactions) {
78 Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
79 if(!target)
80 continue;
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];
86 }
87 else {
88 [target untrigger: self];
89 }
90 [target setRunning: [subaction active]];
91 }
92
93 if ([mainAction isKindOfClass: [JSActionAnalog class]]) {
94 double realValue = [(JSActionAnalog*)mainAction getRealValue: IOHIDValueGetIntegerValue(value)];
95 [target setInputValue: realValue];
96
97 // Add to list of running targets
98 if ([target isContinuous] && [target running]) {
99 if (!objInArray([self runningTargets], target)) {
100 [[self runningTargets] addObject: target];
101 }
102 }
103 }
104 }
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];
108 if(!handler)
109 return;
110
111 [self expandRecursive: handler];
112 self->programmaticallySelecting = YES;
113 [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
114 }
115 }
116
117 int findAvailableIndex(id list, Joystick* js) {
118 BOOL available;
119 Joystick* js2;
120 for(int index=0;;index++) {
121 available = YES;
122 for(int i=0; i<[list count]; i++) {
123 js2 = list[i];
124 if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
125 available = NO;
126 break;
127 }
128 }
129 if(available)
130 return index;
131 }
132 }
133
134 void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
135 JoystickController* self = (__bridge JoystickController*)inContext;
136
137 IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
138 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) CFBridgingRetain(self));
139
140 Joystick *js = [[Joystick alloc] initWithDevice: device];
141 [js setIndex: findAvailableIndex([self joysticks], js)];
142
143 [js populateActions];
144
145 [[self joysticks] addObject: js];
146 [self->outlineView reloadData];
147 }
148
149 -(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
150 for (Joystick *js in joysticks)
151 if (js.device == device)
152 return js;
153 return nil;
154 }
155
156 void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
157 JoystickController* self = CFBridgingRelease(inContext);
158
159 Joystick* match = [self findJoystickByRef: device];
160 if(!match)
161 return;
162
163 [[self joysticks] removeObject: match];
164
165 [match invalidate];
166 [self->outlineView reloadData];
167 }
168
169 -(void) setup {
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)];
174
175 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)CFBridgingRetain(criteria));
176
177 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
178 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
179 (void)tIOReturn;
180
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
185
186
187
188 // Setup timer for continuous targets
189 CFRunLoopTimerContext ctx = {
190 0, (__bridge void*)self, NULL, NULL, NULL
191 };
192 CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault,
193 CFAbsoluteTimeGetCurrent(), 1.0/80.0,
194 0, 0, timer_callback, &ctx);
195 CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
196 }
197
198 -(id) determineSelectedAction {
199 id item = [outlineView itemAtRow: [outlineView selectedRow]];
200 if(!item)
201 return NULL;
202 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
203 return NULL;
204 if([item isKindOfClass: [Joystick class]])
205 return NULL;
206 return item;
207 }
208
209 /* outline view */
210
211 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
212 if(item == nil)
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];
218 return 0;
219 }
220
221 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
222 if(item == nil)
223 return YES;
224 if([item isKindOfClass: [Joystick class]])
225 return YES;
226 if([item isKindOfClass: [JSAction class]])
227 return [item subActions]==NULL ? NO : YES;
228 return NO;
229 }
230
231 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
232 if(item == nil)
233 return joysticks[index];
234
235 if([item isKindOfClass: [Joystick class]])
236 return [item children][index];
237
238 if([item isKindOfClass: [JSAction class]])
239 return [item subActions][index];
240
241 return NULL;
242 }
243 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
244 if(item == nil)
245 return @"root";
246 return [item name];
247 }
248
249 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
250 [targetController reset];
251 selectedAction = [self determineSelectedAction];
252 [targetController load];
253 if(programmaticallySelecting)
254 [targetController focusKey];
255 programmaticallySelecting = NO;
256 }
257
258 @end