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