Better constructor for Config.
[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 "Config.h"
11 #import "ConfigsController.h"
12 #import "Joystick.h"
13 #import "JSAction.h"
14 #import "Target.h"
15 #import "TargetController.h"
16
17 @implementation JoystickController {
18 IOHIDManagerRef hidManager;
19 NSTimer *continuousTimer;
20 NSMutableArray *runningTargets;
21 }
22
23 @synthesize joysticks;
24 @synthesize selectedAction;
25 @synthesize frontWindowOnly;
26 @synthesize mouseLoc;
27 @synthesize sendingRealEvents;
28
29 - (id)init {
30 if ((self = [super init])) {
31 joysticks = [[NSMutableArray alloc] initWithCapacity:16];
32 runningTargets = [[NSMutableArray alloc] initWithCapacity:32];
33 }
34 return self;
35 }
36
37 - (void)dealloc {
38 [continuousTimer invalidate];
39 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
40 CFRelease(hidManager);
41 }
42
43 - (void)expandRecursive:(id)handler {
44 if ([handler base])
45 [self expandRecursive:[handler base]];
46 [outlineView expandItem:handler];
47 }
48
49 - (void)addRunningTarget:(Target *)target {
50 if (![runningTargets containsObject:target]) {
51 [runningTargets addObject:target];
52 }
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 (controller.sendingRealEvents) {
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 JSAction *handler = [js handlerForEvent:value];
82 if (!handler)
83 return;
84
85 [controller expandRecursive:handler];
86 [controller->outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[controller->outlineView rowForItem:handler]] byExtendingSelection: NO];
87 [controller->targetController focusKey];
88 }
89 }
90
91 static int findAvailableIndex(NSArray *list, Joystick *js) {
92 for (int index = 1; ; index++) {
93 BOOL available = YES;
94 for (Joystick *used in list) {
95 if ([used.productName isEqualToString:js.productName] && used.index == index) {
96 available = NO;
97 break;
98 }
99 }
100 if (available)
101 return index;
102 }
103 }
104
105 static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
106 JoystickController *controller = (__bridge JoystickController *)ctx;
107 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)controller);
108 Joystick *js = [[Joystick alloc] initWithDevice:device];
109 js.index = findAvailableIndex(controller.joysticks, js);
110 [[controller joysticks] addObject:js];
111 [controller->outlineView reloadData];
112 }
113
114 - (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
115 for (Joystick *js in joysticks)
116 if (js.device == device)
117 return js;
118 return nil;
119 }
120
121 static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
122 JoystickController *controller = (__bridge JoystickController *)ctx;
123 Joystick *match = [controller findJoystickByRef:device];
124 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
125 if (match) {
126 [controller.joysticks removeObject:match];
127 [controller->outlineView reloadData];
128 }
129 }
130
131 - (void)updateContinuousActions:(NSTimer *)timer {
132 self.mouseLoc = [NSEvent mouseLocation];
133 for (Target *target in [runningTargets copy]) {
134 if (![target update:self]) {
135 [runningTargets removeObject:target];
136 NSLog(@"Removing action, now running %lu.", runningTargets.count);
137 }
138 }
139 if (!runningTargets.count) {
140 [continuousTimer invalidate];
141 continuousTimer = nil;
142 NSLog(@"Unscheduled continuous target timer.");
143 }
144 }
145
146 #define NSSTR(e) ((NSString *)CFSTR(e))
147
148 - (void)setup {
149 hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
150 NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
151 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
152 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
153 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
154 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
155 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
156 ];
157 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef)criteria);
158
159 IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
160 IOReturn ret = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
161 if (ret != kIOReturnSuccess) {
162 [[NSAlert alertWithMessageText:@"Input devices are unavailable"
163 defaultButton:nil
164 alternateButton:nil
165 otherButton:nil
166 informativeTextWithFormat:@"Error 0x%08x occured trying to access your devices. "
167 @"Input may not be correctly detected or mapped.",
168 ret]
169 runModal];
170 }
171
172 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self);
173 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
174 }
175
176 - (Config *)currentConfig {
177 return configsController.currentConfig;
178 }
179
180 - (JSAction *)selectedAction {
181 id item = [outlineView itemAtRow:outlineView.selectedRow];
182 return [item children] ? nil : item;
183 }
184
185 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
186 return item ? [[item children] count] : [joysticks count];
187 }
188
189 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
190 return item ? [[item children] count] > 0: YES;
191 }
192
193 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
194 return item ? [item children][index] : joysticks[index];
195 }
196
197 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
198 if(item == nil)
199 return @"root";
200 return [item name];
201 }
202
203 - (void)outlineViewSelectionDidChange: (NSNotification*) notification {
204 [targetController reset];
205 [targetController load];
206 }
207
208 @end