flightgear/3rdparty/joystick/jsMacOSX.cxx
2022-10-20 20:29:11 +08:00

510 lines
14 KiB
C++

/*
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 <mach/mach.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/hid/IOHIDLib.h>
#include <mach/mach_error.h>
#include <IOKit/hid/IOHIDKeys.h>
#include <IOKit/IOCFPlugIn.h>
#include <CoreFoundation/CoreFoundation.h>
#ifdef MACOS_10_0_4
# include <IOKit/hidsystem/IOHIDUsageTables.h>
#else
/* The header was moved here in MacOS X 10.1 */
# include <Kernel/IOKit/hidsystem/IOHIDUsageTables.h>
#endif
#include <simgear/debug/logstream.hxx>
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<os_specific_s*>(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;
os->hidDev = nullptr;
// 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]);
if (!props) {
// TODO ERROR REPORT
return;
}
// 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<jsJoystick*>(vjs)->
os->parseElement( static_cast<jsJoystick*>(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_INFO, "jsJoystick: input type element has unhandled 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, "jsJoystick: 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_INFO, "jsJoystick: Force feedback and related data ignored");
} else
SG_LOG(SG_INPUT, SG_INFO, "jsJoystick: input type element has unhnadled HID page:" << page);
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; b<num_buttons; ++b) {
(*(os->hidDev))->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; a<real_num_axes; ++a) {
(*(os->hidDev))->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;
}
}
}