Oolite 1.91.0.7644-241112-7f5034b
Loading...
Searching...
No Matches
OOMacJoystickManager.m
Go to the documentation of this file.
1/*
2
3OOMacJoystickManager.m
4By Alex Smith and Jens Ayton
5
6Oolite
7Copyright (C) 2004-2013 Giles C Williams and contributors
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22MA 02110-1301, USA.
23
24*/
25
27#import "OOLogging.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
40static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef);
41static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef);
42static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef);
43
44
45@implementation OOMacJoystickManager
46
47- (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- (void) dealloc
90{
91 if (hidManager != NULL) CFRelease(hidManager);
92 if (devices != NULL) CFRelease(devices);
93
94 [super dealloc];
95}
96
97
98- (NSUInteger) joystickCount
99{
100 return CFArrayGetCount(devices);
101}
102
103
104- (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- (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- (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
181static 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
228static 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 {
246 return valueMap8[value];
247 }
248 }
249 return JOYHAT_CENTERED;
250}
251
252
253- (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- (NSString *) nameOfJoystick:(NSUInteger)stickNumber
333{
334 IOHIDDeviceRef device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(devices, stickNumber);
335 return (NSString *)IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
336}
337
338
339
340- (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
351static void HandleDeviceMatchingCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef)
352{
353 [(OOMacJoystickManager *)inContext handleDeviceAttach:inIOHIDDeviceRef];
354}
355
356
357
358static void HandleInputValueCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDValueRef inIOHIDValueRef)
359{
360 [(OOMacJoystickManager *)inContext handleInputEvent:inIOHIDValueRef];
361}
362
363
364
365static void HandleDeviceRemovalCallback(void * inContext, IOReturn inResult, void* inSender, IOHIDDeviceRef inIOHIDDeviceRef)
366{
367 [(OOMacJoystickManager *)inContext handleDeviceRemoval:inIOHIDDeviceRef];
368}
#define EXPECT_NOT(x)
@ JOYHAT_MOTION
@ JOYHAT_DOWN
@ JOYHAT_LEFTDOWN
@ JOYHAT_RIGHTUP
@ JOYHAT_RIGHT
@ JOYHAT_LEFT
@ JOYHAT_LEFTUP
@ JOYHAT_RIGHTDOWN
@ JOYBUTTONUP
@ JOYHAT_CENTERED
@ JOYAXISMOTION
@ JOYBUTTONDOWN
@ JOYHAT_UP
#define OOLogERR(class, format,...)
Definition OOLogging.h:112
BOOL OOLogWillDisplayMessagesInClass(NSString *inMessageClass)
Definition OOLogging.m:144
void OOLogOutdent(void)
Definition OOLogging.m:376
#define OOLog(class, format,...)
Definition OOLogging.h:88
void OOLogIndent(void)
Definition OOLogging.m:366
#define STICK_GAMMA
@ kJoystickGammaTableSize
static void HandleDeviceRemovalCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef)
static void HandleInputValueCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef inIOHIDValueRef)
static void HandleDeviceMatchingCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef)
unsigned count
float y
float x
static int AxisIndex(uint32_t page, uint32_t usage)
CFMutableArrayRef devices
static uint8_t MapHatValue(CFIndex value, CFIndex max)
int gammaTable[kJoystickGammaTableSize]
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque