Forked Enjoy, mouse movement
[enjoyable.git] / JoystickController.m
1 //
2 // JoystickController.m
3 // Enjoy
4 //
5 // Created by Sam McCall on 4/05/09.
6 //
7
8 @implementation JoystickController
9
10 @synthesize joysticks, selectedAction;
11
12 -(id) init {
13 if(self=[super init]) {
14 joysticks = [[NSMutableArray alloc]init];
15 programmaticallySelecting = NO;
16 }
17 return self;
18 }
19
20 -(void) finalize {
21 for(int i=0; i<[joysticks count]; i++) {
22 [[joysticks objectAtIndex:i] invalidate];
23 }
24 IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone);
25 CFRelease(hidManager);
26 [super finalize];
27 }
28
29 static NSMutableDictionary* create_criterion( UInt32 inUsagePage, UInt32 inUsage )
30 {
31 NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
32 [dict setObject: [NSNumber numberWithInt: inUsagePage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsagePageKey)];
33 [dict setObject: [NSNumber numberWithInt: inUsage] forKey: (NSString*)CFSTR(kIOHIDDeviceUsageKey)];
34 return dict;
35 }
36
37 -(void) expandRecursive: (id) handler {
38 if([handler base])
39 [self expandRecursive: [handler base]];
40 [outlineView expandItem: handler];
41 }
42
43 void input_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDValueRef value) {
44 JoystickController* self = (JoystickController*)inContext;
45 IOHIDDeviceRef device = IOHIDQueueGetDevice((IOHIDQueueRef) inSender);
46
47 Joystick* js = [self findJoystickByRef: device];
48 if([[[NSApplication sharedApplication] delegate] active]) {
49 // for reals
50 JSAction* mainAction = [js actionForEvent: value];
51 if(!mainAction)
52 return;
53
54 [mainAction notifyEvent: value];
55 NSArray* subactions = [mainAction subActions];
56 if(!subactions)
57 subactions = [NSArray arrayWithObject:mainAction];
58 for(id subaction in subactions) {
59 Target* target = [[self->configsController currentConfig] getTargetForAction:subaction];
60 if(!target)
61 continue;
62 /* target application? doesn't seem to be any need since we are only active when it's in front */
63 /* might be required for some strange actions */
64 [target setRunning: [subaction active]];
65 [target setInputValue: IOHIDValueGetIntegerValue(value)];
66 }
67 } else if([[NSApplication sharedApplication] isActive] && [[[NSApplication sharedApplication]mainWindow]isVisible]) {
68 // joysticks not active, use it to select stuff
69 id handler = [js handlerForEvent: value];
70 if(!handler)
71 return;
72
73 [self expandRecursive: handler];
74 self->programmaticallySelecting = YES;
75 [self->outlineView selectRowIndexes: [NSIndexSet indexSetWithIndex: [self->outlineView rowForItem: handler]] byExtendingSelection: NO];
76 }
77 }
78
79 int findAvailableIndex(id list, Joystick* js) {
80 BOOL available;
81 Joystick* js2;
82 for(int index=0;;index++) {
83 available = YES;
84 for(int i=0; i<[list count]; i++) {
85 js2 = [list objectAtIndex: i];
86 if([js2 vendorId] == [js vendorId] && [js2 productId] == [js productId] && [js index] == index) {
87 available = NO;
88 break;
89 }
90 }
91 if(available)
92 return index;
93 }
94 }
95
96 void add_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
97 JoystickController* self = (JoystickController*)inContext;
98
99 IOHIDDeviceOpen(device, kIOHIDOptionsTypeNone);
100 IOHIDDeviceRegisterInputValueCallback(device, input_callback, (void*) self);
101
102 Joystick *js = [[Joystick alloc] initWithDevice: device];
103 [js setIndex: findAvailableIndex([self joysticks], js)];
104
105 [js populateActions];
106
107 [[self joysticks] addObject: js];
108 [self->outlineView reloadData];
109 }
110
111 -(Joystick*) findJoystickByRef: (IOHIDDeviceRef) device {
112 for(int i=0; i<[joysticks count]; i++)
113 if([[joysticks objectAtIndex:i] device] == device)
114 return [joysticks objectAtIndex:i];
115 return NULL;
116 }
117
118 void remove_callback(void* inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef device) {
119 JoystickController* self = (JoystickController*)inContext;
120
121 Joystick* match = [self findJoystickByRef: device];
122 if(!match)
123 return;
124
125 [[self joysticks] removeObject: match];
126
127 [match invalidate];
128 [self->outlineView reloadData];
129 }
130
131 -(void) setup {
132 hidManager = IOHIDManagerCreate( kCFAllocatorDefault, kIOHIDOptionsTypeNone);
133 NSArray *criteria = [NSArray arrayWithObjects:
134 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick),
135 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad),
136 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController),
137 create_criterion(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard),
138 nil];
139
140 IOHIDManagerSetDeviceMatchingMultiple(hidManager, (CFArrayRef)criteria);
141
142 IOHIDManagerScheduleWithRunLoop( hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
143 IOReturn tIOReturn = IOHIDManagerOpen( hidManager, kIOHIDOptionsTypeNone );
144 (void)tIOReturn;
145
146 IOHIDManagerRegisterDeviceMatchingCallback( hidManager, add_callback, (void*)self );
147 IOHIDManagerRegisterDeviceRemovalCallback(hidManager, remove_callback, (void*) self);
148 // IOHIDManagerRegisterInputValueCallback(hidManager, input_callback, (void*)self);
149 // register individually so we can find the device more easily
150 }
151
152 -(id) determineSelectedAction {
153 id item = [outlineView itemAtRow: [outlineView selectedRow]];
154 if(!item)
155 return NULL;
156 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
157 return NULL;
158 if([item isKindOfClass: [Joystick class]])
159 return NULL;
160 return item;
161 }
162
163 /* outline view */
164
165 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
166 if(item == nil)
167 return [joysticks count];
168 if([item isKindOfClass: [Joystick class]])
169 return [[item children] count];
170 if([item isKindOfClass: [JSAction class]] && [item subActions] != NULL)
171 return [[item subActions] count];
172 return 0;
173 }
174
175 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
176 if(item == nil)
177 return YES;
178 if([item isKindOfClass: [Joystick class]])
179 return YES;
180 if([item isKindOfClass: [JSAction class]])
181 return [item subActions]==NULL ? NO : YES;
182 return NO;
183 }
184
185 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item {
186 if(item == nil)
187 return [joysticks objectAtIndex: index];
188
189 if([item isKindOfClass: [Joystick class]])
190 return [[item children] objectAtIndex: index];
191
192 if([item isKindOfClass: [JSAction class]])
193 return [[item subActions] objectAtIndex:index];
194
195 return NULL;
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 selectedAction = [self determineSelectedAction];
206 [targetController load];
207 if(programmaticallySelecting)
208 [targetController focusKey];
209 programmaticallySelecting = NO;
210 }
211
212 @end