/* PLIB - A Suite of Portable Game Libraries Copyright (C) 1998,2002 Steve Baker This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA For further information visit http://plib.sourceforge.net $Id: jsMacOSX.cxx 2165 2011-01-22 22:56:03Z fayjf $ */ #include "FlightGear_js.h" #include #include #include #include #include #include #include #ifdef MACOS_10_0_4 # include #else /* The header was moved here in MacOS X 10.1 */ # include #endif #include static const int kNumDevices = 32; static int numDevices = -1; static io_object_t ioDevices[kNumDevices]; static int NS_hat[8] = {1, 1, 0, -1, -1, -1, 0, 1}; static int WE_hat[8] = {0, 1, 1, 1, 0, -1, -1, -1}; struct os_specific_s { IOHIDDeviceInterface ** hidDev; IOHIDElementCookie buttonCookies[41]; IOHIDElementCookie axisCookies[_JS_MAX_AXES]; IOHIDElementCookie hatCookies[_JS_MAX_HATS]; int num_hats; long hat_min[_JS_MAX_HATS]; long hat_max[_JS_MAX_HATS]; bool removed = false; void enumerateElements(jsJoystick* joy, CFTypeRef element); static void elementEnumerator( const void *element, void* vjs); /// callback for CFArrayApply void parseElement(jsJoystick* joy, CFDictionaryRef element); void addAxisElement(jsJoystick* joy, CFDictionaryRef axis); void addButtonElement(jsJoystick* joy, CFDictionaryRef button); void addHatElement(jsJoystick* joy, CFDictionaryRef hat); }; static void findDevices(mach_port_t); static CFDictionaryRef getCFProperties(io_object_t); void jsInit() { if (numDevices < 0) { numDevices = 0; mach_port_t masterPort; IOReturn rv = IOMasterPort(bootstrap_port, &masterPort); if (rv != kIOReturnSuccess) { jsSetError(SG_WARN, "error getting master Mach port"); return; } findDevices(masterPort); } } void jsShutdown() { numDevices = -1; } /** open the IOKit connection, enumerate all the HID devices, add their interface references to the static array. We then use the array index as the device number when we come to open() the joystick. */ static void findDevices(mach_port_t masterPort) { CFMutableDictionaryRef hidMatch = NULL; IOReturn rv = kIOReturnSuccess; io_iterator_t hidIterator; // build a dictionary matching HID devices hidMatch = IOServiceMatching(kIOHIDDeviceKey); rv = IOServiceGetMatchingServices(masterPort, hidMatch, &hidIterator); if (rv != kIOReturnSuccess || !hidIterator) { jsSetError(SG_WARN, "no joystick (HID) devices found"); return; } // iterate io_object_t ioDev; while ((ioDev = IOIteratorNext(hidIterator))) { // filter out keyboard and mouse devices CFDictionaryRef properties = getCFProperties(ioDev); long usage, page; CFTypeRef refPage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsagePageKey)); CFTypeRef refUsage = CFDictionaryGetValue (properties, CFSTR(kIOHIDPrimaryUsageKey)); CFNumberGetValue((CFNumberRef) refUsage, kCFNumberLongType, &usage); CFNumberGetValue((CFNumberRef) refPage, kCFNumberLongType, &page); // keep only joystick devices if ( (page == kHIDPage_GenericDesktop) && ((usage == kHIDUsage_GD_Joystick) || (usage == kHIDUsage_GD_GamePad) // || (usage == kHIDUsage_GD_MultiAxisController) // || (usage == kHIDUsage_GD_Hatswitch) ) ) { // add it to the array ioDevices[numDevices++] = ioDev; } } IOObjectRelease(hidIterator); } static void joystickRemovalCallback(void* target, IOReturn result, void* refCon, void* sender) { os_specific_s* ourJS = reinterpret_cast(target); ourJS->removed = true; } jsJoystick::jsJoystick(int ident) : id(ident), os(NULL), error(JS_FALSE), num_axes(0), num_buttons(0) { if (ident >= numDevices) { setError(); return; } // since the JoystickINput code tries to re-open devices every few seconds, // we need to watch out for removed devices here. if (ioDevices[id] == 0) { setError(); return; } os = new struct os_specific_s; os->num_hats = 0; // get the name now too CFDictionaryRef properties = getCFProperties(ioDevices[id]); CFTypeRef ref = CFDictionaryGetValue (properties, CFSTR(kIOHIDProductKey)); if (!ref) ref = CFDictionaryGetValue (properties, CFSTR("USB Product Name")); if (!ref || !CFStringGetCString ((CFStringRef) ref, name, 128, CFStringGetSystemEncoding ())) { jsSetError(SG_WARN, "error getting device name"); name[0] = '\0'; } //printf("Joystick name: %s \n", name); open(); } void jsJoystick::open() { #if 0 // test already done in the constructor if (id >= numDevices) { jsSetError(SG_WARN, "device index out of range in jsJoystick::open"); return; } #endif // create device interface IOReturn rv; SInt32 score; IOCFPlugInInterface **plugin; rv = IOCreatePlugInInterfaceForService(ioDevices[id], kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); if (rv != kIOReturnSuccess) { jsSetError(SG_WARN, "error creting plugin for io device"); return; } HRESULT pluginResult = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID*)&(os->hidDev) ); if (pluginResult != S_OK) jsSetError(SG_WARN, "QI-ing IO plugin to HID Device interface failed"); (*plugin)->Release(plugin); // don't leak a ref if (os->hidDev == NULL) return; // store the interface in this instance rv = (*(os->hidDev))->open(os->hidDev, 0); if (rv != kIOReturnSuccess) { jsSetError(SG_WARN, "error opening device interface"); return; } rv = (*(os->hidDev))->setRemovalCallback(os->hidDev, &joystickRemovalCallback, os, nullptr); CFDictionaryRef props = getCFProperties(ioDevices[id]); // recursively enumerate all the bits (buttons, axes, hats, ...) CFTypeRef topLevelElement = CFDictionaryGetValue (props, CFSTR(kIOHIDElementKey)); os->enumerateElements(this, topLevelElement); CFRelease(props); // for hats to be implemented as axes: must be the last axes: for (int h = 0; h<2*os->num_hats; h++) { int index = num_axes++; dead_band [ index ] = 0.0f ; saturate [ index ] = 1.0f ; center [ index ] = 0.0f; max [ index ] = 1.0f; min [ index ] = -1.0f; } } CFDictionaryRef getCFProperties(io_object_t ioDev) { IOReturn rv; CFMutableDictionaryRef cfProperties; #if 0 // comment copied from darwin/SDL_sysjoystick.c /* Mac OS X currently is not mirroring all USB properties to HID page so need to look at USB device page also * get dictionary for usb properties: step up two levels and get CF dictionary for USB properties */ io_registry_entry_t parent1, parent2; rv = IORegistryEntryGetParentEntry (ioDev, kIOServicePlane, &parent1); if (rv != kIOReturnSuccess) { jsSetError(SG_WARN, "error getting device entry parent"); return NULL; } rv = IORegistryEntryGetParentEntry (parent1, kIOServicePlane, &parent2); if (rv != kIOReturnSuccess) { jsSetError(SG_WARN, "error getting device entry parent 2"); return NULL; } #endif rv = IORegistryEntryCreateCFProperties( ioDev /*parent2*/, &cfProperties, kCFAllocatorDefault, kNilOptions); if (rv != kIOReturnSuccess || !cfProperties) { jsSetError(SG_WARN, "error getting device properties"); return NULL; } return cfProperties; } void jsJoystick::close() { // check for double-close if (!os) return; if (os->hidDev != NULL) (*(os->hidDev))->close(os->hidDev); if (os) { delete os; os = nullptr; } } /** element enumerator function : pass NULL for top-level*/ void os_specific_s::enumerateElements(jsJoystick* joy, CFTypeRef element) { assert(CFGetTypeID(element) == CFArrayGetTypeID()); CFRange range = {0, CFArrayGetCount ((CFArrayRef)element)}; CFArrayApplyFunction((CFArrayRef) element, range, &elementEnumerator, joy); } void os_specific_s::elementEnumerator( const void *element, void* vjs) { if (CFGetTypeID((CFTypeRef) element) != CFDictionaryGetTypeID()) { jsSetError(SG_WARN, "element enumerator passed non-dictionary value"); return; } static_cast(vjs)-> os->parseElement( static_cast(vjs), (CFDictionaryRef) element); } void os_specific_s::parseElement(jsJoystick* joy, CFDictionaryRef element) { CFTypeRef refPage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsagePageKey)); CFTypeRef refUsage = CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementUsageKey)); long type, page, usage; CFNumberGetValue((CFNumberRef) CFDictionaryGetValue ((CFDictionaryRef) element, CFSTR(kIOHIDElementTypeKey)), kCFNumberLongType, &type); switch (type) { case kIOHIDElementTypeInput_Misc: case kIOHIDElementTypeInput_Axis: case kIOHIDElementTypeInput_Button: //printf("got input element..."); CFNumberGetValue((CFNumberRef) refUsage, kCFNumberLongType, &usage); CFNumberGetValue((CFNumberRef) refPage, kCFNumberLongType, &page); if (page == kHIDPage_GenericDesktop) { switch (usage) /* look at usage to determine function */ { case kHIDUsage_GD_X: case kHIDUsage_GD_Y: case kHIDUsage_GD_Z: case kHIDUsage_GD_Rx: case kHIDUsage_GD_Ry: case kHIDUsage_GD_Rz: case kHIDUsage_GD_Slider: // for throttle / trim controls case kHIDUsage_GD_Dial: //printf(" axis\n"); /*joy->os->*/addAxisElement(joy, (CFDictionaryRef) element); break; case kHIDUsage_GD_Hatswitch: //printf(" hat\n"); /*joy->os->*/addHatElement(joy, (CFDictionaryRef) element); break; default: SG_LOG(SG_INPUT, SG_WARN, "input type element has weird usage:" << usage); break; } } else if (page == kHIDPage_Simulation) { switch (usage) /* look at usage to determine function */ { case kHIDUsage_Sim_Rudder: case kHIDUsage_Sim_Throttle: //printf(" axis\n"); /*joy->os->*/addAxisElement(joy, (CFDictionaryRef) element); break; default: SG_LOG(SG_INPUT, SG_WARN, "Simulation page input type element has weird usage:" << usage); } } else if (page == kHIDPage_Button) { //printf(" button\n"); /*joy->os->*/addButtonElement(joy, (CFDictionaryRef) element); } else if (page == kHIDPage_PID) { SG_LOG(SG_INPUT, SG_WARN, "Force feedback and related data ignored"); } else SG_LOG(SG_INPUT, SG_WARN, "input type element has weird usage:" << usage); break; case kIOHIDElementTypeCollection: /*joy->os->*/enumerateElements(joy, CFDictionaryGetValue(element, CFSTR(kIOHIDElementKey)) ); break; default: break; } } void os_specific_s::addAxisElement(jsJoystick* joy, CFDictionaryRef axis) { long cookie, lmin, lmax; CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (axis, CFSTR(kIOHIDElementCookieKey)), kCFNumberLongType, &cookie); int index = joy->num_axes++; /*joy->os->*/axisCookies[index] = (IOHIDElementCookie) cookie; CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (axis, CFSTR(kIOHIDElementMinKey)), kCFNumberLongType, &lmin); CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (axis, CFSTR(kIOHIDElementMaxKey)), kCFNumberLongType, &lmax); joy->min[index] = lmin; joy->max[index] = lmax; joy->dead_band[index] = 0.0; joy->saturate[index] = 1.0; joy->center[index] = (lmax - lmin) * 0.5 + lmin; } void os_specific_s::addButtonElement(jsJoystick* joy, CFDictionaryRef button) { long cookie; CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (button, CFSTR(kIOHIDElementCookieKey)), kCFNumberLongType, &cookie); /*joy->os->*/buttonCookies[joy->num_buttons++] = (IOHIDElementCookie) cookie; // anything else for buttons? } void os_specific_s::addHatElement(jsJoystick* joy, CFDictionaryRef hat) { long cookie, lmin, lmax; CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (hat, CFSTR(kIOHIDElementCookieKey)), kCFNumberLongType, &cookie); int index = /*joy->*/num_hats++; /*joy->os->*/hatCookies[index] = (IOHIDElementCookie) cookie; CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (hat, CFSTR(kIOHIDElementMinKey)), kCFNumberLongType, &lmin); CFNumberGetValue ((CFNumberRef) CFDictionaryGetValue (hat, CFSTR(kIOHIDElementMaxKey)), kCFNumberLongType, &lmax); hat_min[index] = lmin; hat_max[index] = lmax; // do we map hats to axes or buttons? // axes; there is room for that: Buttons are limited to 32. // (a joystick with 2 hats will use 16 buttons!) } void jsJoystick::rawRead(int *buttons, float *axes) { if (!os) return; if (buttons) *buttons = 0; if (os->removed) { setError(); close(); // clear out from the static array, in case someone tries to open us // again ioDevices[id] = 0; return; } IOHIDEventStruct hidEvent; for (int b=0; bhidDev))->getElementValue(os->hidDev, os->buttonCookies[b], &hidEvent); if (hidEvent.value && buttons) *buttons |= 1 << b; } // real axes: int real_num_axes = num_axes - 2*os->num_hats; for (int a=0; ahidDev))->getElementValue(os->hidDev, os->axisCookies[a], &hidEvent); axes[a] = hidEvent.value; } // hats: for (int h=0; h < os->num_hats; ++h) { (*(os->hidDev))->getElementValue(os->hidDev, os->hatCookies[h], &hidEvent); long result = ( hidEvent.value - os->hat_min[h] ) * 8; result /= ( os->hat_max[h] - os->hat_min[h] + 1 ); if ( (result>=0) && (result<8) ) { axes[h+real_num_axes+1] = NS_hat[result]; axes[h+real_num_axes] = WE_hat[result]; } else { axes[h+real_num_axes] = 0; axes[h+real_num_axes+1] = 0; } } }