1 /*
2 PLIB - A Suite of Portable Game Libraries
3 Copyright (C) 1998,2002 Steve Baker
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19 For further information visit http://plib.sourceforge.net
20
21 $Id: jsMacOSX.cxx 2165 2011-01-22 22:56:03Z fayjf $
22 */
23
24 #include "FlightGear_js.h"
25
26 #include <mach/mach.h>
27 #include <IOKit/IOKitLib.h>
28 #include <IOKit/hid/IOHIDLib.h>
29 #include <mach/mach_error.h>
30 #include <IOKit/hid/IOHIDKeys.h>
31 #include <IOKit/IOCFPlugIn.h>
32 #include <CoreFoundation/CoreFoundation.h>
33
34 #ifdef MACOS_10_0_4
35 # include <IOKit/hidsystem/IOHIDUsageTables.h>
36 #else
37 /* The header was moved here in MacOS X 10.1 */
38 # include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
39 #endif
40
41 #include <simgear/debug/logstream.hxx>
42
43 static const int kNumDevices = 32;
44 static int numDevices = -1;
45 static io_object_t ioDevices[kNumDevices];
46
47 static int NS_hat[8] = {1, 1, 0, -1, -1, -1, 0, 1};
48 static int WE_hat[8] = {0, 1, 1, 1, 0, -1, -1, -1};
49
50 struct os_specific_s {
51 IOHIDDeviceInterface ** hidDev;
52 IOHIDElementCookie buttonCookies[41];
53 IOHIDElementCookie axisCookies[_JS_MAX_AXES];
54 IOHIDElementCookie hatCookies[_JS_MAX_HATS];
55 int num_hats;
56 long hat_min[_JS_MAX_HATS];
57 long hat_max[_JS_MAX_HATS];
58
59 bool removed = false;
60
61 void enumerateElements(jsJoystick* joy, CFTypeRef element);
62 static void elementEnumerator( const void *element, void* vjs);
63 /// callback for CFArrayApply
64 void parseElement(jsJoystick* joy, CFDictionaryRef element);
65 void addAxisElement(jsJoystick* joy, CFDictionaryRef axis);
66 void addButtonElement(jsJoystick* joy, CFDictionaryRef button);
67 void addHatElement(jsJoystick* joy, CFDictionaryRef hat);
68 };
69
70 static void findDevices(mach_port_t);
71 static CFDictionaryRef getCFProperties(io_object_t);
72
73
jsInit()74 void jsInit()
75 {
76 if (numDevices < 0) {
77 numDevices = 0;
78
79 mach_port_t masterPort;
80 IOReturn rv = IOMasterPort(bootstrap_port, &masterPort);
81 if (rv != kIOReturnSuccess) {
82 jsSetError(SG_WARN, "error getting master Mach port");
83 return;
84 }
85
86 findDevices(masterPort);
87 }
88 }
89
jsShutdown()90 void jsShutdown()
91 {
92 numDevices = -1;
93 }
94
95 /** open the IOKit connection, enumerate all the HID devices, add their
96 interface references to the static array. We then use the array index
97 as the device number when we come to open() the joystick. */
findDevices(mach_port_t masterPort)98 static void findDevices(mach_port_t masterPort)
99 {
100 CFMutableDictionaryRef hidMatch = NULL;
101 IOReturn rv = kIOReturnSuccess;
102 io_iterator_t hidIterator;
103
104 // build a dictionary matching HID devices
105 hidMatch = IOServiceMatching(kIOHIDDeviceKey);
106
107 rv = IOServiceGetMatchingServices(masterPort, hidMatch, &hidIterator);
108 if (rv != kIOReturnSuccess || !hidIterator) {
109 jsSetError(SG_WARN, "no joystick (HID) devices found");
110 return;
111 }
112
113 // iterate
114 io_object_t ioDev;
115
116 while ((ioDev = IOIteratorNext(hidIterator))) {
117 // filter out keyboard and mouse devices
118 CFDictionaryRef properties = getCFProperties(ioDev);
119 long usage, page;
120
121 CFTypeRef refPage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsagePageKey));
122 CFTypeRef refUsage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsageKey));
123 CFNumberGetValue((CFNumberRef) refUsage, kCFNumberLongType, &usage);
124 CFNumberGetValue((CFNumberRef) refPage, kCFNumberLongType, &page);
125
126 // keep only joystick devices
127 if ( (page == kHIDPage_GenericDesktop) &&
128 ((usage == kHIDUsage_GD_Joystick) ||
129 (usage == kHIDUsage_GD_GamePad)
130 // || (usage == kHIDUsage_GD_MultiAxisController)
131 // || (usage == kHIDUsage_GD_Hatswitch)
132 )
133 )
134 {
135 // add it to the array
136 ioDevices[numDevices++] = ioDev;
137 }
138 }
139
140 IOObjectRelease(hidIterator);
141 }
142
joystickRemovalCallback(void * target,IOReturn result,void * refCon,void * sender)143 static void joystickRemovalCallback(void* target, IOReturn result, void* refCon, void* sender)
144 {
145 os_specific_s* ourJS = reinterpret_cast<os_specific_s*>(target);
146 ourJS->removed = true;
147 }
148
jsJoystick(int ident)149 jsJoystick::jsJoystick(int ident) :
150 id(ident),
151 os(NULL),
152 error(JS_FALSE),
153 num_axes(0),
154 num_buttons(0)
155 {
156 if (ident >= numDevices) {
157 setError();
158 return;
159 }
160
161 // since the JoystickINput code tries to re-open devices every few seconds,
162 // we need to watch out for removed devices here.
163 if (ioDevices[id] == 0) {
164 setError();
165 return;
166 }
167
168 os = new struct os_specific_s;
169 os->num_hats = 0;
170 os->hidDev = nullptr;
171
172 // get the name now too
173 CFDictionaryRef properties = getCFProperties(ioDevices[id]);
174 CFTypeRef ref = CFDictionaryGetValue (properties, CFSTR(kIOHIDProductKey));
175 if (!ref)
176 ref = CFDictionaryGetValue (properties, CFSTR("USB Product Name"));
177
178 if (!ref || !CFStringGetCString ((CFStringRef) ref, name, 128, CFStringGetSystemEncoding ())) {
179 jsSetError(SG_WARN, "error getting device name");
180 name[0] = '\0';
181 }
182 //printf("Joystick name: %s \n", name);
183 open();
184 }
185
open()186 void jsJoystick::open()
187 {
188 #if 0 // test already done in the constructor
189 if (id >= numDevices) {
190 jsSetError(SG_WARN, "device index out of range in jsJoystick::open");
191 return;
192 }
193 #endif
194
195 // create device interface
196 IOReturn rv;
197 SInt32 score;
198 IOCFPlugInInterface **plugin;
199
200 rv = IOCreatePlugInInterfaceForService(ioDevices[id],
201 kIOHIDDeviceUserClientTypeID,
202 kIOCFPlugInInterfaceID,
203 &plugin, &score);
204
205 if (rv != kIOReturnSuccess) {
206 jsSetError(SG_WARN, "error creting plugin for io device");
207 return;
208 }
209
210 HRESULT pluginResult = (*plugin)->QueryInterface(plugin,
211 CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID*)&(os->hidDev) );
212
213 if (pluginResult != S_OK)
214 jsSetError(SG_WARN, "QI-ing IO plugin to HID Device interface failed");
215
216 (*plugin)->Release(plugin); // don't leak a ref
217 if (os->hidDev == NULL) return;
218
219 // store the interface in this instance
220 rv = (*(os->hidDev))->open(os->hidDev, 0);
221 if (rv != kIOReturnSuccess) {
222 jsSetError(SG_WARN, "error opening device interface");
223 return;
224 }
225
226 rv = (*(os->hidDev))->setRemovalCallback(os->hidDev, &joystickRemovalCallback, os, nullptr);
227
228 CFDictionaryRef props = getCFProperties(ioDevices[id]);
229 if (!props) {
230 // TODO ERROR REPORT
231 return;
232 }
233
234 // recursively enumerate all the bits (buttons, axes, hats, ...)
235 CFTypeRef topLevelElement =
236 CFDictionaryGetValue (props, CFSTR(kIOHIDElementKey));
237 os->enumerateElements(this, topLevelElement);
238 CFRelease(props);
239
240 // for hats to be implemented as axes: must be the last axes:
241 for (int h = 0; h<2*os->num_hats; h++)
242 {
243 int index = num_axes++;
244 dead_band [ index ] = 0.0f ;
245 saturate [ index ] = 1.0f ;
246 center [ index ] = 0.0f;
247 max [ index ] = 1.0f;
248 min [ index ] = -1.0f;
249 }
250 }
251
getCFProperties(io_object_t ioDev)252 CFDictionaryRef getCFProperties(io_object_t ioDev)
253 {
254 IOReturn rv;
255 CFMutableDictionaryRef cfProperties;
256
257 #if 0
258 // comment copied from darwin/SDL_sysjoystick.c
259 /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also
260 * get dictionary for usb properties: step up two levels and get CF dictionary for USB properties
261 */
262
263 io_registry_entry_t parent1, parent2;
264
265 rv = IORegistryEntryGetParentEntry (ioDev, kIOServicePlane, &parent1);
266 if (rv != kIOReturnSuccess) {
267 jsSetError(SG_WARN, "error getting device entry parent");
268 return NULL;
269 }
270
271 rv = IORegistryEntryGetParentEntry (parent1, kIOServicePlane, &parent2);
272 if (rv != kIOReturnSuccess) {
273 jsSetError(SG_WARN, "error getting device entry parent 2");
274 return NULL;
275 }
276
277 #endif
278 rv = IORegistryEntryCreateCFProperties( ioDev /*parent2*/,
279 &cfProperties, kCFAllocatorDefault, kNilOptions);
280 if (rv != kIOReturnSuccess || !cfProperties) {
281 jsSetError(SG_WARN, "error getting device properties");
282 return NULL;
283 }
284
285 return cfProperties;
286 }
287
close()288 void jsJoystick::close()
289 {
290 // check for double-close
291 if (!os)
292 return;
293
294 if (os->hidDev != NULL) (*(os->hidDev))->close(os->hidDev);
295
296 if (os) {
297 delete os;
298 os = nullptr;
299 }
300 }
301
302 /** element enumerator function : pass NULL for top-level*/
enumerateElements(jsJoystick * joy,CFTypeRef element)303 void os_specific_s::enumerateElements(jsJoystick* joy, CFTypeRef element)
304 {
305 assert(CFGetTypeID(element) == CFArrayGetTypeID());
306
307 CFRange range = {0, CFArrayGetCount ((CFArrayRef)element)};
308 CFArrayApplyFunction((CFArrayRef) element, range,
309 &elementEnumerator, joy);
310 }
311
elementEnumerator(const void * element,void * vjs)312 void os_specific_s::elementEnumerator( const void *element, void* vjs)
313 {
314 if (CFGetTypeID((CFTypeRef) element) != CFDictionaryGetTypeID()) {
315 jsSetError(SG_WARN, "element enumerator passed non-dictionary value");
316 return;
317 }
318
319 static_cast<jsJoystick*>(vjs)->
320 os->parseElement( static_cast<jsJoystick*>(vjs), (CFDictionaryRef) element);
321 }
322
parseElement(jsJoystick * joy,CFDictionaryRef element)323 void os_specific_s::parseElement(jsJoystick* joy, CFDictionaryRef element)
324 {
325 CFTypeRef refPage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsagePageKey));
326 CFTypeRef refUsage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsageKey));
327
328 long type, page, usage;
329
330 CFNumberGetValue((CFNumberRef)
331 CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementTypeKey)),
332 kCFNumberLongType, &type);
333
334 switch (type) {
335 case kIOHIDElementTypeInput_Misc:
336 case kIOHIDElementTypeInput_Axis:
337 case kIOHIDElementTypeInput_Button:
338 //printf("got input element...");
339 CFNumberGetValue((CFNumberRef) refUsage, kCFNumberLongType, &usage);
340 CFNumberGetValue((CFNumberRef) refPage, kCFNumberLongType, &page);
341
342 if (page == kHIDPage_GenericDesktop) {
343 switch (usage) /* look at usage to determine function */
344 {
345 case kHIDUsage_GD_X:
346 case kHIDUsage_GD_Y:
347 case kHIDUsage_GD_Z:
348 case kHIDUsage_GD_Rx:
349 case kHIDUsage_GD_Ry:
350 case kHIDUsage_GD_Rz:
351 case kHIDUsage_GD_Slider: // for throttle / trim controls
352 case kHIDUsage_GD_Dial:
353 //printf(" axis\n");
354 /*joy->os->*/addAxisElement(joy, (CFDictionaryRef) element);
355 break;
356
357 case kHIDUsage_GD_Hatswitch:
358 //printf(" hat\n");
359 /*joy->os->*/addHatElement(joy, (CFDictionaryRef) element);
360 break;
361 default:
362 SG_LOG(SG_INPUT, SG_INFO, "jsJoystick: input type element has unhandled usage:" << usage);
363 break;
364 }
365 } else if (page == kHIDPage_Simulation) {
366 switch (usage) /* look at usage to determine function */
367 {
368 case kHIDUsage_Sim_Rudder:
369 case kHIDUsage_Sim_Throttle:
370 //printf(" axis\n");
371 /*joy->os->*/addAxisElement(joy, (CFDictionaryRef) element);
372 break;
373 default:
374 SG_LOG(SG_INPUT, SG_WARN, "jsJoystick: Simulation page input type element has weird usage:" << usage);
375 }
376 } else if (page == kHIDPage_Button) {
377 //printf(" button\n");
378 /*joy->os->*/addButtonElement(joy, (CFDictionaryRef) element);
379 } else if (page == kHIDPage_PID) {
380 SG_LOG(SG_INPUT, SG_INFO, "jsJoystick: Force feedback and related data ignored");
381 } else
382 SG_LOG(SG_INPUT, SG_INFO, "jsJoystick: input type element has unhnadled HID page:" << page);
383 break;
384
385 case kIOHIDElementTypeCollection:
386 /*joy->os->*/enumerateElements(joy,
387 CFDictionaryGetValue(element, CFSTR(kIOHIDElementKey))
388 );
389 break;
390
391 default:
392 break;
393 }
394 }
395
addAxisElement(jsJoystick * joy,CFDictionaryRef axis)396 void os_specific_s::addAxisElement(jsJoystick* joy, CFDictionaryRef axis)
397 {
398 long cookie, lmin, lmax;
399 CFNumberGetValue ((CFNumberRef)
400 CFDictionaryGetValue (axis, CFSTR(kIOHIDElementCookieKey)),
401 kCFNumberLongType, &cookie);
402
403 int index = joy->num_axes++;
404
405 /*joy->os->*/axisCookies[index] = (IOHIDElementCookie) cookie;
406
407 CFNumberGetValue ((CFNumberRef)
408 CFDictionaryGetValue (axis, CFSTR(kIOHIDElementMinKey)),
409 kCFNumberLongType, &lmin);
410
411 CFNumberGetValue ((CFNumberRef)
412 CFDictionaryGetValue (axis, CFSTR(kIOHIDElementMaxKey)),
413 kCFNumberLongType, &lmax);
414
415 joy->min[index] = lmin;
416 joy->max[index] = lmax;
417 joy->dead_band[index] = 0.0;
418 joy->saturate[index] = 1.0;
419 joy->center[index] = (lmax - lmin) * 0.5 + lmin;
420 }
421
addButtonElement(jsJoystick * joy,CFDictionaryRef button)422 void os_specific_s::addButtonElement(jsJoystick* joy, CFDictionaryRef button)
423 {
424 long cookie;
425 CFNumberGetValue ((CFNumberRef)
426 CFDictionaryGetValue (button, CFSTR(kIOHIDElementCookieKey)),
427 kCFNumberLongType, &cookie);
428
429 /*joy->os->*/buttonCookies[joy->num_buttons++] = (IOHIDElementCookie) cookie;
430 // anything else for buttons?
431 }
432
addHatElement(jsJoystick * joy,CFDictionaryRef hat)433 void os_specific_s::addHatElement(jsJoystick* joy, CFDictionaryRef hat)
434 {
435 long cookie, lmin, lmax;
436 CFNumberGetValue ((CFNumberRef)
437 CFDictionaryGetValue (hat, CFSTR(kIOHIDElementCookieKey)),
438 kCFNumberLongType, &cookie);
439
440 int index = /*joy->*/num_hats++;
441
442 /*joy->os->*/hatCookies[index] = (IOHIDElementCookie) cookie;
443
444 CFNumberGetValue ((CFNumberRef)
445 CFDictionaryGetValue (hat, CFSTR(kIOHIDElementMinKey)),
446 kCFNumberLongType, &lmin);
447
448 CFNumberGetValue ((CFNumberRef)
449 CFDictionaryGetValue (hat, CFSTR(kIOHIDElementMaxKey)),
450 kCFNumberLongType, &lmax);
451
452 hat_min[index] = lmin;
453 hat_max[index] = lmax;
454 // do we map hats to axes or buttons?
455 // axes; there is room for that: Buttons are limited to 32.
456 // (a joystick with 2 hats will use 16 buttons!)
457 }
458
rawRead(int * buttons,float * axes)459 void jsJoystick::rawRead(int *buttons, float *axes)
460 {
461 if (!os)
462 return;
463
464 if (buttons)
465 *buttons = 0;
466
467 if (os->removed) {
468 setError();
469 close();
470
471 // clear out from the static array, in case someone tries to open us
472 // again
473 ioDevices[id] = 0;
474
475 return;
476 }
477
478 IOHIDEventStruct hidEvent;
479
480 for (int b=0; b<num_buttons; ++b) {
481 (*(os->hidDev))->getElementValue(os->hidDev, os->buttonCookies[b], &hidEvent);
482 if (hidEvent.value && buttons)
483 *buttons |= 1 << b;
484 }
485
486 // real axes:
487 int real_num_axes = num_axes - 2*os->num_hats;
488 for (int a=0; a<real_num_axes; ++a) {
489 (*(os->hidDev))->getElementValue(os->hidDev, os->axisCookies[a], &hidEvent);
490 axes[a] = hidEvent.value;
491 }
492
493 // hats:
494 for (int h=0; h < os->num_hats; ++h) {
495 (*(os->hidDev))->getElementValue(os->hidDev, os->hatCookies[h], &hidEvent);
496 long result = ( hidEvent.value - os->hat_min[h] ) * 8;
497 result /= ( os->hat_max[h] - os->hat_min[h] + 1 );
498 if ( (result>=0) && (result<8) )
499 {
500 axes[h+real_num_axes+1] = NS_hat[result];
501 axes[h+real_num_axes] = WE_hat[result];
502 }
503 else
504 {
505 axes[h+real_num_axes] = 0;
506 axes[h+real_num_axes+1] = 0;
507 }
508 }
509 }
510