Clean up JoystickController. Modernize more Objective-C syntax. Remove direct public...
[enjoyable.git] / JoystickController.m
1 //
2 // JoystickController.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 #import "JoystickController.h"
9
10 @implementation JoystickController {
11 IOHIDManagerRef hidManager;
12 BOOL programmaticallySelecting;
13 NSTimer *continuousTimer;
14 }
15
16 @synthesize joysticks;
17 @synthesize runningTargets;
18 @synthesize selectedAction;
19 @synthesize frontWindowOnly;
20 @synthesize mouseLoc;
21
22 - (id)init {
23 if ((self = [super init])) {
24 joysticks = [[NSMutableArray alloc] initWithCapacity:16];
25 runningTargets = [[NSMutableArray alloc] initWithCapacity:32];
26 }
27 return self;
28 }
29
30 - (void)dealloc {
31 [continuousTimer invalidate];
32 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
33 CFRelease(hidManager);
34 }
35
36 - (void)expandRecursive:(id)handler {
37 if ([handler base])
38 [self expandRecursive:[handler base]];
39 [outlineView expandItem:handler];
40 }
41
42 - (void)addRunningTarget:(Target *)target {
43 if (![runningTargets containsObject:target])
44 [runningTargets addObject:target];
45 if (!continuousTimer) {
46 continuousTimer = [NSTimer scheduledTimerWithTimeInterval:1.f/60.f
47 target:self
48 selector:@selector(updateContinuousActions:)
49 userInfo:nil
50 repeats:YES];
51 NSLog(@"Scheduled continuous target timer.");
52 }
53 }
54
55 static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) {
56 JoystickController *controller = (__bridge JoystickController *)ctx;
57 IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender);
58
59 Joystick *js = [controller findJoystickByRef:device];
60 if (((ApplicationController *)[NSApplication sharedApplication].delegate).active) {
61 JSAction *mainAction = [js actionForEvent:value];
62 if (!mainAction)
63 return;
64
65 [mainAction notifyEvent:value];
66 NSArray *children = mainAction.children ? mainAction.children : @[mainAction];
67 for (JSAction *subaction in children) {
68 Target *target = [[controller->configsController currentConfig] getTargetForAction:subaction];
69 if (!target)
70 continue;
71 // TODO: Can we just trigger based on setRunning:?
72 if (target.running != subaction.active) {
73 if (subaction.active)
74 [target trigger:controller];
75 else
76 [target untrigger:controller];
77 target.running = subaction.active;
78 }
79
80 // FIXME: Hack, should just expose analog info properly in continuous target.
81 if ([mainAction isKindOfClass:[JSActionAnalog class]]) {
82 double realValue = [(JSActionAnalog *)mainAction getRealValue:IOHIDValueGetIntegerValue(value)];
83 [target setInputValue:realValue];
84 if (target.isContinuous && target.running)
85 [controller addRunningTarget:target];
86 }
87 }
88 } else if ([NSApplication sharedApplication].isActive && [NSApplication sharedApplication].mainWindow.isVisible) {
89 // joysticks not active, use it to select stuff
90 JSAction *handler = [js handlerForEvent:value];
91 if (!handler)
92 return;
93
94 [controller expandRecursive:handler];
95 controller->programmaticallySelecting = YES;
96 [controller->outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[controller->outlineView rowForItem:handler]] byExtendingSelection: NO];
97 }
98 }
99
100 static int findAvailableIndex(NSArray *list, Joystick *js) {
101 for (int index = 0; ; index++) {
102 BOOL available = YES;
103 for (Joystick *used in list) {
104 if ([used.productName isEqualToString:js.productName] && used.index == index) {
105 available = NO;
106 break;
107 }
108 }
109 if (available)
110 return index;
111 }
112 }
113
114 static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
115 JoystickController *controller = (__bridge JoystickController *)ctx;
116 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)controller);
117 Joystick *js = [[Joystick alloc] initWithDevice:device];
118 js.index = findAvailableIndex(controller.joysticks, js);
119 [[controller joysticks] addObject:js];
120 [controller->outlineView reloadData];
121 }
122
123 - (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
124 for (Joystick *js in joysticks)
125 if (js.device == device)
126 return js;
127 return nil;
128 }
129
130 static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
131 JoystickController *controller = (__bridge JoystickController *)ctx;
132 Joystick *match = [controller findJoystickByRef:device];
133 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
134 if (match) {
135 [controller.joysticks removeObject:match];
136 [controller->outlineView reloadData];
137 }
138 }
139
140 - (void)updateContinuousActions:(NSTimer *)timer {
141 self.mouseLoc = [NSEvent mouseLocation];
142 for (Target *target in [self.runningTargets copy]) {
143 if (![target update:self])
144 [self.runningTargets removeObject:target];
145 }
146 if (!self.runningTargets.count) {
147 [continuousTimer invalidate];
148 continuousTimer = nil;
149 NSLog(@"Unscheduled continuous target timer.");
150 }
151 }
152
153 #define NSSTR(e) ((NSString *)CFSTR(e))
154
155 - (void)setup {
156 hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
157 NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
158 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
159 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
160 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
161 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
162 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
163 ];
164 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef)criteria);
165
166 IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
167 IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); // FIXME: If an error happens, report it!
168
169 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self);
170 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
171 }
172
173 - (JSAction *)selectedAction {
174 id item = [outlineView itemAtRow:outlineView.selectedRow];
175 return [item children] ? nil : item;
176 }
177
178 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
179 return item ? [[item children] count] : [joysticks count];
180 }
181
182 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
183 return item ? [[item children] count] > 0: YES;
184 }
185
186 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
187 return item ? [item children][index] : joysticks[index];
188 }
189
190 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
191 if(item == nil)
192 return @"root";
193 return [item name];
194 }
195
196 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
197 [targetController reset];
198 [targetController load];
199 if (programmaticallySelecting)
200 [targetController focusKey];
201 programmaticallySelecting = NO;
202 }
203
204 @end