2 // JoystickController.m
5 // Created by Sam McCall on 4/05/09.
8 #import "JoystickController.h"
11 #import "ConfigsController.h"
15 #import "TargetController.h"
18 @implementation JoystickController {
19 IOHIDManagerRef hidManager;
20 NSTimer *continuousTimer;
21 NSMutableArray *runningTargets;
22 NSMutableArray *_joysticks;
26 if ((self = [super init])) {
27 _joysticks = [[NSMutableArray alloc] initWithCapacity:16];
28 runningTargets = [[NSMutableArray alloc] initWithCapacity:32];
34 [continuousTimer invalidate];
35 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
36 CFRelease(hidManager);
39 - (void)expandRecursive:(id <NJActionPathElement>)pathElement {
41 [self expandRecursive:pathElement.base];
42 [outlineView expandItem:pathElement];
46 - (void)addRunningTarget:(Target *)target {
47 if (![runningTargets containsObject:target]) {
48 [runningTargets addObject:target];
50 if (!continuousTimer) {
51 continuousTimer = [NSTimer scheduledTimerWithTimeInterval:1.f/60.f
53 selector:@selector(updateContinuousActions:)
56 NSLog(@"Scheduled continuous target timer.");
60 - (void)runTargetForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
61 Joystick *js = [self findJoystickByRef:device];
62 JSAction *mainAction = [js actionForEvent:value];
63 [mainAction notifyEvent:value];
64 NSArray *children = mainAction.children ? mainAction.children : mainAction ? @[mainAction] : @[];
65 for (JSAction *subaction in children) {
66 Target *target = configsController.currentConfig[subaction];
67 target.magnitude = mainAction.magnitude;
68 target.running = subaction.active;
69 if (target.running && target.isContinuous)
70 [self addRunningTarget:target];
74 - (void)showTargetForDevice:(IOHIDDeviceRef)device value:(IOHIDValueRef)value {
75 Joystick *js = [self findJoystickByRef:device];
76 JSAction *handler = [js handlerForEvent:value];
80 [self expandRecursive:handler];
81 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:[outlineView rowForItem:handler]] byExtendingSelection: NO];
82 [targetController focusKey];
85 static void input_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDValueRef value) {
86 JoystickController *controller = (__bridge JoystickController *)ctx;
87 IOHIDDeviceRef device = IOHIDQueueGetDevice(inSender);
89 if (controller.translatingEvents) {
90 [controller runTargetForDevice:device value:value];
91 } else if ([NSApplication sharedApplication].mainWindow.isVisible) {
92 [controller showTargetForDevice:device value:value];
96 static int findAvailableIndex(NSArray *list, Joystick *js) {
97 for (int index = 1; ; index++) {
99 for (Joystick *used in list) {
100 if ([used.productName isEqualToString:js.productName] && used.index == index) {
110 - (void)addJoystickForDevice:(IOHIDDeviceRef)device {
111 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (__bridge void*)self);
112 Joystick *js = [[Joystick alloc] initWithDevice:device];
113 js.index = findAvailableIndex(_joysticks, js);
114 [_joysticks addObject:js];
115 [outlineView reloadData];
118 static void add_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
119 JoystickController *controller = (__bridge JoystickController *)ctx;
120 [controller addJoystickForDevice:device];
123 - (Joystick *)findJoystickByRef:(IOHIDDeviceRef)device {
124 for (Joystick *js in _joysticks)
125 if (js.device == device)
130 static void remove_callback(void *ctx, IOReturn inResult, void *inSender, IOHIDDeviceRef device) {
131 JoystickController *controller = (__bridge JoystickController *)ctx;
132 [controller removeJoystickForDevice:device];
135 - (void)removeJoystickForDevice:(IOHIDDeviceRef)device {
136 Joystick *match = [self findJoystickByRef:device];
137 IOHIDDeviceRegisterInputValueCallback(device, NULL, NULL);
139 [_joysticks removeObject:match];
140 [outlineView reloadData];
145 - (void)updateContinuousActions:(NSTimer *)timer {
146 self.mouseLoc = [NSEvent mouseLocation];
147 for (Target *target in [runningTargets copy]) {
148 if (![target update:self]) {
149 [runningTargets removeObject:target];
152 if (!runningTargets.count) {
153 [continuousTimer invalidate];
154 continuousTimer = nil;
155 NSLog(@"Unscheduled continuous target timer.");
159 #define NSSTR(e) ((NSString *)CFSTR(e))
162 hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
163 NSArray *criteria = @[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
164 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_Joystick) },
165 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
166 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_GamePad) },
167 @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop),
168 NSSTR(kIOHIDDeviceUsageKey) : @(kHIDUsage_GD_MultiAxisController) }
170 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef)criteria);
172 IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
173 IOReturn ret = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
174 if (ret != kIOReturnSuccess) {
175 [[NSAlert alertWithMessageText:@"Input devices are unavailable"
179 informativeTextWithFormat:@"Error 0x%08x occured trying to access your devices. "
180 @"Input may not be correctly detected or mapped.",
182 beginSheetModalForWindow:outlineView.window
188 IOHIDManagerRegisterDeviceMatchingCallback(hidManager, add_callback, (__bridge void *)self);
189 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (__bridge void *)self);
192 - (JSAction *)selectedAction {
193 id <NJActionPathElement> item = [outlineView itemAtRow:outlineView.selectedRow];
194 return (!item.children && item.base) ? item : nil;
197 - (NSInteger)outlineView:(NSOutlineView *)outlineView
198 numberOfChildrenOfItem:(id <NJActionPathElement>)item {
199 return item ? item.children.count : _joysticks.count;
202 - (BOOL)outlineView:(NSOutlineView *)outlineView
203 isItemExpandable:(id <NJActionPathElement>)item {
204 return item ? [[item children] count] > 0: YES;
207 - (id)outlineView:(NSOutlineView *)outlineView
208 child:(NSInteger)index
209 ofItem:(id <NJActionPathElement>)item {
210 return item ? item.children[index] : _joysticks[index];
213 - (id)outlineView:(NSOutlineView *)outlineView
214 objectValueForTableColumn:(NSTableColumn *)tableColumn
215 byItem:(id <NJActionPathElement>)item {
216 return item ? item.name : @"root";
219 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
221 [targetController loadCurrent];
224 - (void)setTranslatingEvents:(BOOL)translatingEvents {
225 if (translatingEvents != _translatingEvents) {
226 _translatingEvents = translatingEvents;
227 NSString *name = translatingEvents
228 ? NJEventTranslationActivated
229 : NJEventTranslationDeactivated;
230 [NSNotificationCenter.defaultCenter postNotificationName:name