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