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