Line data Source code
1 0 : /*
2 :
3 : OOMacJoystickManager.m
4 : By Alex Smith and Jens Ayton
5 :
6 : Oolite
7 : Copyright (C) 2004-2013 Giles C Williams and contributors
8 :
9 : This program is free software; you can redistribute it and/or
10 : modify it under the terms of the GNU General Public License
11 : as published by the Free Software Foundation; either version 2
12 : of the License, or (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program; if not, write to the Free Software
21 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 : MA 02110-1301, USA.
23 :
24 : */
25 :
26 : #import "OOMacJoystickManager.h"
27 : #import "OOLogging.h"
28 : #import "OOCollectionExtractors.h"
29 :
30 :
31 : @interface OOMacJoystickManager ()
32 :
33 : - (void) handleInputEvent:(IOHIDValueRef)value;
34 : - (void) handleJoystickAttach:(IOHIDDeviceRef)device;
35 : - (void) handleDeviceRemoval:(IOHIDDeviceRef)device;
36 :
37 : @end
38 :
39 :
40 : static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef);
41 : static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef);
42 : static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef);
43 :
44 :
45 : @implementation OOMacJoystickManager
46 :
47 0 : - (id) init
48 : {
49 : if ((self = [super init]))
50 : {
51 : // Initialise gamma table
52 : int i;
53 : for (i = 0; i< kJoystickGammaTableSize; i++)
54 : {
55 : double x = ((double) i - 128.0) / 128.0;
56 : double sign = x>=0 ? 1.0 : -1.0;
57 : double y = sign * floor(32767.0 * pow (fabs(x), STICK_GAMMA));
58 : gammaTable[i] = (int) y;
59 : }
60 :
61 : /*
62 : SLOW_CODE
63 : The call to IOHIDManagerSetDeviceMatching() has a significiant
64 : presence in warm startup profiles; sometimes it's even makes
65 : -[OOMacJoystickManager init] the single heaviest Oolite symbol
66 : within -[GameController applicationDidFinishLaunching]. Setup
67 : should be made asynchronous.
68 : -- Ahruman 2012-09-14
69 : */
70 : hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
71 : IOHIDManagerSetDeviceMatching(hidManager, NULL);
72 :
73 : IOHIDManagerRegisterDeviceMatchingCallback(hidManager, HandleDeviceMatchingCallback, self);
74 : IOHIDManagerRegisterDeviceRemovalCallback(hidManager, HandleDeviceRemovalCallback, self);
75 :
76 : IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
77 : IOReturn iores = IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone);
78 : if (iores != kIOReturnSuccess)
79 : {
80 : OOLog(@"joystick.error.init", @"Cannot open HID manager; joystick support will not function.");
81 : }
82 :
83 : devices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
84 : }
85 : return self;
86 : }
87 :
88 :
89 0 : - (void) dealloc
90 : {
91 : if (hidManager != NULL) CFRelease(hidManager);
92 : if (devices != NULL) CFRelease(devices);
93 :
94 : [super dealloc];
95 : }
96 :
97 :
98 0 : - (NSUInteger) joystickCount
99 : {
100 : return CFArrayGetCount(devices);
101 : }
102 :
103 :
104 0 : - (void) handleDeviceAttach:(IOHIDDeviceRef)device
105 : {
106 : NSArray *usagePairs = (NSArray *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDDeviceUsagePairsKey));
107 :
108 : for (NSDictionary *pair in usagePairs)
109 : {
110 : if ([pair oo_unsignedIntForKey:@kIOHIDDeviceUsagePageKey] == kHIDPage_GenericDesktop)
111 : {
112 : unsigned usage = [pair oo_unsignedIntForKey:@kIOHIDDeviceUsageKey];
113 : if (usage == kHIDUsage_GD_Joystick || usage == kHIDUsage_GD_GamePad)
114 : {
115 : [self handleJoystickAttach:device];
116 : return;
117 : }
118 : }
119 : }
120 :
121 : if (OOLogWillDisplayMessagesInClass(@"joystick.reject"))
122 : {
123 : NSString *product = (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
124 : unsigned usagePage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsagePageKey)) unsignedIntValue];
125 : unsigned usage = [(NSNumber *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDPrimaryUsageKey)) unsignedIntValue];
126 : OOLog(@"joystick.reject", @"Ignoring HID device: %@ (primary usage %u:%u)", product, usagePage, usage);
127 : }
128 : }
129 :
130 :
131 0 : - (void) handleJoystickAttach:(IOHIDDeviceRef)device
132 : {
133 : OOLog(@"joystick.connect", @"Joystick connected: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)));
134 :
135 : CFArrayAppendValue(devices, device);
136 : IOHIDDeviceRegisterInputValueCallback(device, HandleInputValueCallback, self);
137 :
138 : if (OOLogWillDisplayMessagesInClass(@"joystick.connect.element"))
139 : {
140 : // Print out elements of new device
141 : CFArrayRef elementList = IOHIDDeviceCopyMatchingElements(device, NULL, 0L);
142 : if (elementList != NULL)
143 : {
144 : OOLogIndent();
145 :
146 : CFIndex idx, count = CFArrayGetCount(elementList);
147 :
148 : for (idx = 0; idx < count; idx++)
149 : {
150 : IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elementList, idx);
151 : IOHIDElementType elementType = IOHIDElementGetType(element);
152 : if (elementType > kIOHIDElementTypeInput_ScanCodes)
153 : {
154 : continue;
155 : }
156 : IOHIDElementCookie elementCookie = IOHIDElementGetCookie(element);
157 : uint32_t usagePage = IOHIDElementGetUsagePage(element);
158 : uint32_t usage = IOHIDElementGetUsage(element);
159 : uint32_t min = (uint32_t)IOHIDElementGetPhysicalMin(element);
160 : uint32_t max = (uint32_t)IOHIDElementGetPhysicalMax(element);
161 : NSString *name = (NSString *)IOHIDElementGetProperty(element, CFSTR(kIOHIDElementNameKey)) ?: @"unnamed";
162 : OOLog(@"joystick.connect.element", @"%@ - usage %d:%d, cookie %d, range %d-%d", name, usagePage, usage, (int) elementCookie, min, max);
163 : }
164 :
165 : OOLogOutdent();
166 : CFRelease(elementList);
167 : }
168 : }
169 : }
170 :
171 :
172 0 : - (void) handleDeviceRemoval:(IOHIDDeviceRef)device
173 : {
174 : OOLog(@"joystick.remove", @"Joystick removed: %@", IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)));
175 :
176 : CFIndex index = CFArrayGetFirstIndexOfValue(devices, CFRangeMake(0, CFArrayGetCount(devices)), device);
177 : if (index != kCFNotFound) CFArrayRemoveValueAtIndex(devices, index);
178 : }
179 :
180 :
181 0 : static int AxisIndex(uint32_t page, uint32_t usage)
182 : {
183 : /*
184 : Map axis-like HID usages to SDL-like axis indices. These are all the
185 : commonly used joystick axes according to Microsoft's DirectInput
186 : documentation (hey, you've got to get your info somewhere).
187 :
188 : GD_Slider, GD_Dial, DG_Wheel and Sim_Throttle are actually distinct;
189 : unlike the others, they're uncentered. (By implication,
190 : IOHIDElementHasPreferredState() should be false.) This should, in
191 : particular, be considered when mapping to the throttle: centered axes
192 : should provide relative input (for gamepads), while uncentered ones
193 : should provide absolute input. Since that festering pool of pus, SDL,
194 : can't make this distinction, OOJoystickManager can't either (yet).
195 : -- Ahruman 2011-01-04
196 : */
197 :
198 : switch (page)
199 : {
200 : case kHIDPage_GenericDesktop:
201 : switch (usage)
202 : {
203 : case kHIDUsage_GD_X: return 0;
204 : case kHIDUsage_GD_Y: return 1;
205 : case kHIDUsage_GD_Z: return 2;
206 : case kHIDUsage_GD_Rx: return 3;
207 : case kHIDUsage_GD_Ry: return 4;
208 : case kHIDUsage_GD_Rz: return 5;
209 : case kHIDUsage_GD_Slider: return 6;
210 : case kHIDUsage_GD_Dial: return 7;
211 : case kHIDUsage_GD_Wheel: return 8;
212 : }
213 : break;
214 :
215 : case kHIDPage_Simulation:
216 : switch (usage)
217 : {
218 : case kHIDUsage_Sim_Throttle:
219 : return 9;
220 : }
221 : }
222 :
223 : // Negative numbers indicate non-axis.
224 : return -1;
225 : }
226 :
227 :
228 0 : static uint8_t MapHatValue(CFIndex value, CFIndex max)
229 : {
230 : /*
231 : A hat switch has four or eight values, indicating directions. 0
232 : is straight up/forwards, and subsequent values increase clockwise.
233 : Out-of-range values are nulls, indicating no direction is pressed.
234 : */
235 :
236 : if (0 <= value && value <= max)
237 : {
238 : if (max == 3)
239 : {
240 : const uint8_t valueMap4[4] = { JOYHAT_UP, JOYHAT_RIGHT, JOYHAT_DOWN, JOYHAT_LEFT };
241 : return valueMap4[value];
242 : }
243 : else if (max == 7)
244 : {
245 : const uint8_t valueMap8[8] = { JOYHAT_UP, JOYHAT_RIGHTUP, JOYHAT_RIGHT, JOYHAT_RIGHTDOWN, JOYHAT_DOWN, JOYHAT_LEFTDOWN, JOYHAT_LEFT, JOYHAT_LEFTUP };
246 : return valueMap8[value];
247 : }
248 : }
249 : return JOYHAT_CENTERED;
250 : }
251 :
252 :
253 0 : - (void) handleInputEvent:(IOHIDValueRef)value
254 : {
255 : IOHIDElementRef element = IOHIDValueGetElement(value);
256 : uint32_t usagePage = IOHIDElementGetUsagePage(element);
257 : uint32_t usage = IOHIDElementGetUsage(element);
258 : int buttonNum = 0;
259 : int axisNum = 0;
260 :
261 : axisNum = AxisIndex(usagePage, usage);
262 : if (axisNum >= 0)
263 : {
264 : JoyAxisEvent evt;
265 : evt.type = JOYAXISMOTION;
266 : evt.which = 0;
267 : evt.axis = axisNum;
268 :
269 : CFIndex intValue = IOHIDValueGetIntegerValue(value);
270 : CFIndex min = IOHIDElementGetLogicalMin(element);
271 : CFIndex max = IOHIDElementGetLogicalMax(element);
272 : float axisValue = (float)(intValue - min) / (float)(max + 1 - min);
273 :
274 : // Note: this is designed so gammaIndex == intValue if min == 0 and max == kJoystickGammaTableSize - 1 (the common case).
275 : unsigned gammaIndex = floor(axisValue * kJoystickGammaTableSize);
276 :
277 : /*
278 : CRASH: r4435, Mac OS X 10.6.6, x86_64 -[OOMacJoystickManager handleInputEvent:] + 260
279 : http://aegidian.org/bb/viewtopic.php?f=3&t=9382
280 : The instruction is:
281 : movl (%r15,%rbx,4), %r12d
282 : which corresponds to the load gammaTable[gammaIndex].
283 : Check added to find out-of-range values.
284 : -- Ahruman 2011-03-07
285 : */
286 : if (EXPECT_NOT(gammaIndex > kJoystickGammaTableSize))
287 : {
288 : OOLogERR(@"joystick.gamma.overflow", @"Joystick gamma table overflow - gammaIndex is %u of %u. Raw value: %i in [%i..%i]; axisValue: %g; unrounded gammaIndex: %g. Ignoring event. This is an internal error, please report it.",
289 : gammaIndex, kJoystickGammaTableSize,
290 : (int)intValue, (int)min, (int)max,
291 : axisValue,
292 : axisValue * kJoystickGammaTableSize
293 : );
294 :
295 : return;
296 : }
297 : // End bug check
298 :
299 : evt.value = gammaTable[gammaIndex];
300 : [self decodeAxisEvent:&evt];
301 : }
302 : else if (usagePage == kHIDPage_GenericDesktop && usage == kHIDUsage_GD_Hatswitch)
303 : {
304 : CFIndex max = IOHIDElementGetLogicalMax(element);
305 : CFIndex min = IOHIDElementGetLogicalMin(element);
306 :
307 : JoyHatEvent evt =
308 : {
309 : .type = JOYHAT_MOTION,
310 : .which = 0,
311 : .hat = 0, // The abuse of usage values for identifying elements means we can't distinguish between hats.
312 : .value = MapHatValue(IOHIDValueGetIntegerValue(value) - min, max - min)
313 : };
314 :
315 : [self decodeHatEvent:&evt];
316 : }
317 : else if (usagePage == kHIDPage_Button)
318 : {
319 : // Button Event
320 : buttonNum = usage;
321 : JoyButtonEvent evt;
322 : BOOL buttonState = (IOHIDValueGetIntegerValue(value) != 0);
323 : evt.type = buttonState ? JOYBUTTONDOWN : JOYBUTTONUP;
324 : evt.which = 0;
325 : evt.button = buttonNum;
326 : evt.state = buttonState ? 1 : 0;
327 : [self decodeButtonEvent:&evt];
328 : }
329 : }
330 :
331 :
332 0 : - (NSString *) nameOfJoystick:(NSUInteger)stickNumber
333 : {
334 : IOHIDDeviceRef device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(devices, stickNumber);
335 : return (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
336 : }
337 :
338 :
339 :
340 0 : - (int16_t) getAxisWithStick:(NSUInteger) stickNum axis:(NSUInteger) axisNum
341 : {
342 : return 0;
343 : }
344 :
345 :
346 :
347 : @end
348 :
349 :
350 : //Thunking to Objective-C
351 0 : static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef)
352 : {
353 : [(OOMacJoystickManager *)inContext handleDeviceAttach:inIOHIDDeviceRef];
354 : }
355 :
356 :
357 :
358 0 : static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef)
359 : {
360 : [(OOMacJoystickManager *)inContext handleInputEvent:inIOHIDValueRef];
361 : }
362 :
363 :
364 :
365 0 : static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef)
366 : {
367 : [(OOMacJoystickManager *)inContext handleDeviceRemoval:inIOHIDDeviceRef];
368 : }
|