936 lines
30 KiB
C
936 lines
30 KiB
C
/*
|
|
* WinMM joystick driver OS X implementation
|
|
*
|
|
* Copyright 1997 Andreas Mohr
|
|
* Copyright 1998 Marcus Meissner
|
|
* Copyright 1998,1999 Lionel Ulmer
|
|
* Copyright 2000 Wolfgang Schwotzer
|
|
* Copyright 2000-2001 TransGaming Technologies Inc.
|
|
* Copyright 2002 David Hagood
|
|
* Copyright 2009 CodeWeavers, Aric Stewart
|
|
* Copyright 2015 Ken Thomases for CodeWeavers Inc.
|
|
* Copyright 2016 David Lawrie
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#if defined(HAVE_IOKIT_HID_IOHIDLIB_H)
|
|
|
|
#define DWORD UInt32
|
|
#define LPDWORD UInt32*
|
|
#define LONG SInt32
|
|
#define LPLONG SInt32*
|
|
#define E_PENDING __carbon_E_PENDING
|
|
#define ULONG __carbon_ULONG
|
|
#define E_INVALIDARG __carbon_E_INVALIDARG
|
|
#define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
|
|
#define E_HANDLE __carbon_E_HANDLE
|
|
#define E_ACCESSDENIED __carbon_E_ACCESSDENIED
|
|
#define E_UNEXPECTED __carbon_E_UNEXPECTED
|
|
#define E_FAIL __carbon_E_FAIL
|
|
#define E_ABORT __carbon_E_ABORT
|
|
#define E_POINTER __carbon_E_POINTER
|
|
#define E_NOINTERFACE __carbon_E_NOINTERFACE
|
|
#define E_NOTIMPL __carbon_E_NOTIMPL
|
|
#define S_FALSE __carbon_S_FALSE
|
|
#define S_OK __carbon_S_OK
|
|
#define HRESULT_FACILITY __carbon_HRESULT_FACILITY
|
|
#define IS_ERROR __carbon_IS_ERROR
|
|
#define FAILED __carbon_FAILED
|
|
#define SUCCEEDED __carbon_SUCCEEDED
|
|
#define MAKE_HRESULT __carbon_MAKE_HRESULT
|
|
#define HRESULT __carbon_HRESULT
|
|
#define STDMETHODCALLTYPE __carbon_STDMETHODCALLTYPE
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/hid/IOHIDLib.h>
|
|
#undef ULONG
|
|
#undef E_INVALIDARG
|
|
#undef E_OUTOFMEMORY
|
|
#undef E_HANDLE
|
|
#undef E_ACCESSDENIED
|
|
#undef E_UNEXPECTED
|
|
#undef E_FAIL
|
|
#undef E_ABORT
|
|
#undef E_POINTER
|
|
#undef E_NOINTERFACE
|
|
#undef E_NOTIMPL
|
|
#undef S_FALSE
|
|
#undef S_OK
|
|
#undef HRESULT_FACILITY
|
|
#undef IS_ERROR
|
|
#undef FAILED
|
|
#undef SUCCEEDED
|
|
#undef MAKE_HRESULT
|
|
#undef HRESULT
|
|
#undef STDMETHODCALLTYPE
|
|
#undef DWORD
|
|
#undef LPDWORD
|
|
#undef LONG
|
|
#undef LPLONG
|
|
#undef E_PENDING
|
|
|
|
#include "joystick.h"
|
|
|
|
#include "wine/debug.h"
|
|
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(joystick);
|
|
|
|
|
|
#define MAXJOYSTICK (JOYSTICKID2 + 30)
|
|
|
|
|
|
enum {
|
|
AXIS_X, /* Winmm X */
|
|
AXIS_Y, /* Winmm Y */
|
|
AXIS_Z, /* Winmm Z */
|
|
AXIS_RX, /* Winmm V */
|
|
AXIS_RY, /* Winmm U */
|
|
AXIS_RZ, /* Winmm R */
|
|
NUM_AXES
|
|
};
|
|
|
|
struct axis {
|
|
IOHIDElementRef element;
|
|
CFIndex min_value, max_value;
|
|
};
|
|
|
|
typedef struct {
|
|
BOOL in_use;
|
|
IOHIDElementRef element;
|
|
struct axis axes[NUM_AXES];
|
|
CFMutableArrayRef buttons;
|
|
IOHIDElementRef hatswitch;
|
|
} joystick_t;
|
|
|
|
|
|
static joystick_t joysticks[MAXJOYSTICK];
|
|
static CFMutableArrayRef device_main_elements = NULL;
|
|
|
|
static long get_device_property_long(IOHIDDeviceRef device, CFStringRef key)
|
|
{
|
|
CFTypeRef ref;
|
|
long result = 0;
|
|
|
|
if (device)
|
|
{
|
|
assert(IOHIDDeviceGetTypeID() == CFGetTypeID(device));
|
|
|
|
ref = IOHIDDeviceGetProperty(device, key);
|
|
|
|
if (ref && CFNumberGetTypeID() == CFGetTypeID(ref))
|
|
CFNumberGetValue((CFNumberRef)ref, kCFNumberLongType, &result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static CFStringRef copy_device_name(IOHIDDeviceRef device)
|
|
{
|
|
CFStringRef name;
|
|
|
|
if (device)
|
|
{
|
|
CFTypeRef ref_name;
|
|
|
|
assert(IOHIDDeviceGetTypeID() == CFGetTypeID(device));
|
|
|
|
ref_name = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
|
|
|
if (ref_name && CFStringGetTypeID() == CFGetTypeID(ref_name))
|
|
name = CFStringCreateCopy(kCFAllocatorDefault, ref_name);
|
|
else
|
|
{
|
|
long vendID, prodID;
|
|
|
|
vendID = get_device_property_long(device, CFSTR(kIOHIDVendorIDKey));
|
|
prodID = get_device_property_long(device, CFSTR(kIOHIDProductIDKey));
|
|
name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("0x%04lx 0x%04lx"), vendID, prodID);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR("NULL device\n");
|
|
name = CFStringCreateCopy(kCFAllocatorDefault, CFSTR(""));
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
static long get_device_location_ID(IOHIDDeviceRef device)
|
|
{
|
|
return get_device_property_long(device, CFSTR(kIOHIDLocationIDKey));
|
|
}
|
|
|
|
static const char* debugstr_cf(CFTypeRef t)
|
|
{
|
|
CFStringRef s;
|
|
const char* ret;
|
|
|
|
if (!t) return "(null)";
|
|
|
|
if (CFGetTypeID(t) == CFStringGetTypeID())
|
|
s = t;
|
|
else
|
|
s = CFCopyDescription(t);
|
|
ret = CFStringGetCStringPtr(s, kCFStringEncodingUTF8);
|
|
if (ret) ret = debugstr_a(ret);
|
|
if (!ret)
|
|
{
|
|
const UniChar* u = CFStringGetCharactersPtr(s);
|
|
if (u)
|
|
ret = debugstr_wn((const WCHAR*)u, CFStringGetLength(s));
|
|
}
|
|
if (!ret)
|
|
{
|
|
UniChar buf[200];
|
|
int len = min(CFStringGetLength(s), sizeof(buf)/sizeof(buf[0]));
|
|
CFStringGetCharacters(s, CFRangeMake(0, len), buf);
|
|
ret = debugstr_wn(buf, len);
|
|
}
|
|
if (s != t) CFRelease(s);
|
|
return ret;
|
|
}
|
|
|
|
static const char* debugstr_device(IOHIDDeviceRef device)
|
|
{
|
|
return wine_dbg_sprintf("<IOHIDDevice %p product %s IOHIDLocationID %lu>", device,
|
|
debugstr_cf(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))),
|
|
get_device_location_ID(device));
|
|
}
|
|
|
|
static const char* debugstr_element(IOHIDElementRef element)
|
|
{
|
|
return wine_dbg_sprintf("<IOHIDElement %p type %d usage %u/%u device %p>", element,
|
|
IOHIDElementGetType(element), IOHIDElementGetUsagePage(element),
|
|
IOHIDElementGetUsage(element), IOHIDElementGetDevice(element));
|
|
}
|
|
|
|
static int axis_for_usage_GD(int usage)
|
|
{
|
|
switch (usage)
|
|
{
|
|
case kHIDUsage_GD_X: return AXIS_X;
|
|
case kHIDUsage_GD_Y: return AXIS_Y;
|
|
case kHIDUsage_GD_Z: return AXIS_Z;
|
|
case kHIDUsage_GD_Rx: return AXIS_RX;
|
|
case kHIDUsage_GD_Ry: return AXIS_RY;
|
|
case kHIDUsage_GD_Rz: return AXIS_RZ;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int axis_for_usage_Sim(int usage)
|
|
{
|
|
switch (usage)
|
|
{
|
|
case kHIDUsage_Sim_Rudder: return AXIS_RZ;
|
|
case kHIDUsage_Sim_Throttle: return AXIS_Z;
|
|
case kHIDUsage_Sim_Steering: return AXIS_X;
|
|
case kHIDUsage_Sim_Accelerator: return AXIS_Y;
|
|
case kHIDUsage_Sim_Brake: return AXIS_RZ;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* joystick_from_id
|
|
*/
|
|
static joystick_t* joystick_from_id(DWORD_PTR device_id)
|
|
{
|
|
int index;
|
|
|
|
if ((device_id - (DWORD_PTR)joysticks) % sizeof(joysticks[0]) != 0)
|
|
return NULL;
|
|
index = (device_id - (DWORD_PTR)joysticks) / sizeof(joysticks[0]);
|
|
if (index < 0 || index >= MAXJOYSTICK || !((joystick_t*)device_id)->in_use)
|
|
return NULL;
|
|
|
|
return (joystick_t*)device_id;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* create_osx_device_match
|
|
*/
|
|
static CFDictionaryRef create_osx_device_match(int usage)
|
|
{
|
|
CFDictionaryRef result = NULL;
|
|
int number;
|
|
CFStringRef keys[] = { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) };
|
|
CFNumberRef values[2];
|
|
int i;
|
|
|
|
TRACE("usage %d\n", usage);
|
|
|
|
number = kHIDPage_GenericDesktop;
|
|
values[0] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &number);
|
|
values[1] = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
|
|
|
|
if (values[0] && values[1])
|
|
{
|
|
result = CFDictionaryCreate(NULL, (const void**)keys, (const void**)values, sizeof(values) / sizeof(values[0]),
|
|
&kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
|
|
|
if (!result)
|
|
ERR("CFDictionaryCreate failed.\n");
|
|
}
|
|
else
|
|
ERR("CFNumberCreate failed.\n");
|
|
|
|
for (i = 0; i < sizeof(values) / sizeof(values[0]); i++)
|
|
if (values[i]) CFRelease(values[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* find_top_level
|
|
*/
|
|
static CFIndex find_top_level(IOHIDDeviceRef hid_device, CFMutableArrayRef main_elements)
|
|
{
|
|
CFArrayRef elements;
|
|
CFIndex total = 0;
|
|
|
|
TRACE("hid_device %s\n", debugstr_device(hid_device));
|
|
|
|
if (!hid_device)
|
|
return 0;
|
|
|
|
elements = IOHIDDeviceCopyMatchingElements(hid_device, NULL, 0);
|
|
|
|
if (elements)
|
|
{
|
|
CFIndex i, count = CFArrayGetCount(elements);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, i);
|
|
int type = IOHIDElementGetType(element);
|
|
|
|
TRACE("element %s\n", debugstr_element(element));
|
|
|
|
/* Check for top-level gaming device collections */
|
|
if (type == kIOHIDElementTypeCollection && IOHIDElementGetParent(element) == 0)
|
|
{
|
|
int usage_page = IOHIDElementGetUsagePage(element);
|
|
int usage = IOHIDElementGetUsage(element);
|
|
|
|
if (usage_page == kHIDPage_GenericDesktop &&
|
|
(usage == kHIDUsage_GD_Joystick || usage == kHIDUsage_GD_GamePad || usage == kHIDUsage_GD_MultiAxisController))
|
|
{
|
|
CFArrayAppendValue(main_elements, element);
|
|
total++;
|
|
}
|
|
}
|
|
}
|
|
CFRelease(elements);
|
|
}
|
|
|
|
TRACE("-> total %d\n", (int)total);
|
|
return total;
|
|
}
|
|
/**************************************************************************
|
|
* device_name_comparator
|
|
*
|
|
* Virtual joysticks may not have a kIOHIDLocationIDKey and will default to location ID of 0, this orders virtual joysticks by their name
|
|
*/
|
|
static CFComparisonResult device_name_comparator(IOHIDDeviceRef device1, IOHIDDeviceRef device2)
|
|
{
|
|
CFStringRef name1 = copy_device_name(device1), name2 = copy_device_name(device2);
|
|
CFComparisonResult result = CFStringCompare(name1, name2, (kCFCompareForcedOrdering | kCFCompareNumerically));
|
|
CFRelease(name1);
|
|
CFRelease(name2);
|
|
return result;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* device_location_name_comparator
|
|
*
|
|
* Helper to sort device array first by location ID, since location IDs are consistent across boots & launches, then by product name
|
|
*/
|
|
static CFComparisonResult device_location_name_comparator(const void *val1, const void *val2, void *context)
|
|
{
|
|
IOHIDDeviceRef device1 = (IOHIDDeviceRef)val1, device2 = (IOHIDDeviceRef)val2;
|
|
long loc1 = get_device_location_ID(device1), loc2 = get_device_location_ID(device2);
|
|
|
|
if (loc1 < loc2)
|
|
return kCFCompareLessThan;
|
|
else if (loc1 > loc2)
|
|
return kCFCompareGreaterThan;
|
|
return device_name_comparator(device1, device2);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* copy_set_to_array
|
|
*
|
|
* Helper to copy the CFSet to a CFArray
|
|
*/
|
|
static void copy_set_to_array(const void *value, void *context)
|
|
{
|
|
CFArrayAppendValue(context, value);
|
|
}
|
|
|
|
/**************************************************************************
|
|
* find_osx_devices
|
|
*/
|
|
static int find_osx_devices(void)
|
|
{
|
|
IOHIDManagerRef hid_manager;
|
|
int usages[] = { kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad, kHIDUsage_GD_MultiAxisController };
|
|
int i;
|
|
CFDictionaryRef matching_dicts[sizeof(usages) / sizeof(usages[0])];
|
|
CFArrayRef matching;
|
|
CFSetRef devset;
|
|
|
|
TRACE("()\n");
|
|
|
|
hid_manager = IOHIDManagerCreate(kCFAllocatorDefault, 0L);
|
|
if (IOHIDManagerOpen(hid_manager, 0) != kIOReturnSuccess)
|
|
{
|
|
ERR("Couldn't open IOHIDManager.\n");
|
|
CFRelease(hid_manager);
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(matching_dicts) / sizeof(matching_dicts[0]); i++)
|
|
{
|
|
matching_dicts[i] = create_osx_device_match(usages[i]);
|
|
if (!matching_dicts[i])
|
|
{
|
|
while (i > 0)
|
|
CFRelease(matching_dicts[--i]);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
matching = CFArrayCreate(NULL, (const void**)matching_dicts, sizeof(matching_dicts) / sizeof(matching_dicts[0]),
|
|
&kCFTypeArrayCallBacks);
|
|
|
|
for (i = 0; i < sizeof(matching_dicts) / sizeof(matching_dicts[0]); i++)
|
|
CFRelease(matching_dicts[i]);
|
|
|
|
IOHIDManagerSetDeviceMatchingMultiple(hid_manager, matching);
|
|
CFRelease(matching);
|
|
devset = IOHIDManagerCopyDevices(hid_manager);
|
|
if (devset)
|
|
{
|
|
CFIndex num_devices, num_main_elements;
|
|
CFMutableArrayRef devices;
|
|
|
|
num_devices = CFSetGetCount(devset);
|
|
devices = CFArrayCreateMutable(kCFAllocatorDefault, num_devices, &kCFTypeArrayCallBacks);
|
|
CFSetApplyFunction(devset, copy_set_to_array, (void *)devices);
|
|
CFArraySortValues(devices, CFRangeMake(0, num_devices), device_location_name_comparator, NULL);
|
|
|
|
CFRelease(devset);
|
|
if (!devices)
|
|
goto fail;
|
|
|
|
device_main_elements = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
if (!device_main_elements)
|
|
{
|
|
CFRelease(devices);
|
|
goto fail;
|
|
}
|
|
|
|
num_main_elements = 0;
|
|
for (i = 0; i < num_devices; i++)
|
|
{
|
|
IOHIDDeviceRef hid_device = (IOHIDDeviceRef)CFArrayGetValueAtIndex(devices, i);
|
|
TRACE("hid_device %s\n", debugstr_device(hid_device));
|
|
num_main_elements += find_top_level(hid_device, device_main_elements);
|
|
}
|
|
|
|
CFRelease(devices);
|
|
|
|
TRACE("found %i device(s), %i collection(s)\n",(int)num_devices,(int)num_main_elements);
|
|
return (int)num_main_elements;
|
|
}
|
|
|
|
fail:
|
|
IOHIDManagerClose(hid_manager, 0);
|
|
CFRelease(hid_manager);
|
|
return 0;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* collect_joystick_elements
|
|
*/
|
|
static void collect_joystick_elements(joystick_t* joystick, IOHIDElementRef collection)
|
|
{
|
|
CFIndex i, count;
|
|
CFArrayRef children = IOHIDElementGetChildren(collection);
|
|
|
|
TRACE("collection %s\n", debugstr_element(collection));
|
|
|
|
count = CFArrayGetCount(children);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
IOHIDElementRef child;
|
|
int type;
|
|
uint32_t usage_page;
|
|
|
|
child = (IOHIDElementRef)CFArrayGetValueAtIndex(children, i);
|
|
TRACE("child %s\n", debugstr_element(child));
|
|
type = IOHIDElementGetType(child);
|
|
usage_page = IOHIDElementGetUsagePage(child);
|
|
|
|
switch (type)
|
|
{
|
|
case kIOHIDElementTypeCollection:
|
|
collect_joystick_elements(joystick, child);
|
|
break;
|
|
case kIOHIDElementTypeInput_Button:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Button usage_page %d\n", usage_page);
|
|
|
|
/* avoid strange elements found on the 360 controller */
|
|
if (usage_page == kHIDPage_Button)
|
|
CFArrayAppendValue(joystick->buttons, child);
|
|
break;
|
|
}
|
|
case kIOHIDElementTypeInput_Axis:
|
|
case kIOHIDElementTypeInput_Misc:
|
|
{
|
|
uint32_t usage = IOHIDElementGetUsage( child );
|
|
switch (usage_page)
|
|
{
|
|
case kHIDPage_GenericDesktop:
|
|
{
|
|
switch(usage)
|
|
{
|
|
case kHIDUsage_GD_Hatswitch:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_GD_Hatswitch\n");
|
|
if (joystick->hatswitch)
|
|
TRACE(" ignoring additional hatswitch\n");
|
|
else
|
|
joystick->hatswitch = (IOHIDElementRef)CFRetain(child);
|
|
break;
|
|
}
|
|
case kHIDUsage_GD_X:
|
|
case kHIDUsage_GD_Y:
|
|
case kHIDUsage_GD_Z:
|
|
case kHIDUsage_GD_Rx:
|
|
case kHIDUsage_GD_Ry:
|
|
case kHIDUsage_GD_Rz:
|
|
{
|
|
int axis = axis_for_usage_GD(usage);
|
|
TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_GD_<axis> (%d) axis %d\n", usage, axis);
|
|
if (axis < 0 || joystick->axes[axis].element)
|
|
TRACE(" ignoring\n");
|
|
else
|
|
{
|
|
joystick->axes[axis].element = (IOHIDElementRef)CFRetain(child);
|
|
joystick->axes[axis].min_value = IOHIDElementGetLogicalMin(child);
|
|
joystick->axes[axis].max_value = IOHIDElementGetLogicalMax(child);
|
|
}
|
|
break;
|
|
}
|
|
case kHIDUsage_GD_Slider:
|
|
case kHIDUsage_GD_Dial:
|
|
case kHIDUsage_GD_Wheel:
|
|
{
|
|
/* if one axis is taken, fall to the next until axes are filled */
|
|
int possible_axes[3] = {AXIS_Z,AXIS_RY,AXIS_RX};
|
|
int axis = 0;
|
|
while(axis < 3 && joystick->axes[possible_axes[axis]].element)
|
|
axis++;
|
|
if (axis == 3)
|
|
TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_GD_<axis> (%d)\n ignoring\n", usage);
|
|
else
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_GD_<axis> (%d) axis %d\n", usage, possible_axes[axis]);
|
|
joystick->axes[possible_axes[axis]].element = (IOHIDElementRef)CFRetain(child);
|
|
joystick->axes[possible_axes[axis]].min_value = IOHIDElementGetLogicalMin(child);
|
|
joystick->axes[possible_axes[axis]].max_value = IOHIDElementGetLogicalMax(child);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("kIOHIDElementTypeInput_Axis/Misc / Unhandled GD Page usage %d\n", usage);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case kHIDPage_Simulation:
|
|
{
|
|
switch(usage)
|
|
{
|
|
case kHIDUsage_Sim_Rudder:
|
|
case kHIDUsage_Sim_Throttle:
|
|
case kHIDUsage_Sim_Steering:
|
|
case kHIDUsage_Sim_Accelerator:
|
|
case kHIDUsage_Sim_Brake:
|
|
{
|
|
int axis = axis_for_usage_Sim(usage);
|
|
TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_Sim_<axis> (%d) axis %d\n", usage, axis);
|
|
if (axis < 0 || joystick->axes[axis].element)
|
|
TRACE(" ignoring\n");
|
|
else
|
|
{
|
|
joystick->axes[axis].element = (IOHIDElementRef)CFRetain(child);
|
|
joystick->axes[axis].min_value = IOHIDElementGetLogicalMin(child);
|
|
joystick->axes[axis].max_value = IOHIDElementGetLogicalMax(child);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("kIOHIDElementTypeInput_Axis/Misc / Unhandled Sim Page usage %d\n", usage);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("kIOHIDElementTypeInput_Axis/Misc / Unhandled Usage Page %d\n", usage_page);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case kIOHIDElementTypeFeature:
|
|
/* Describes input and output elements not intended for consumption by the end user. Ignoring. */
|
|
break;
|
|
default:
|
|
FIXME("Unhandled type %i\n",type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
* button_usage_comparator
|
|
*/
|
|
static CFComparisonResult button_usage_comparator(const void *val1, const void *val2, void *context)
|
|
{
|
|
IOHIDElementRef element1 = (IOHIDElementRef)val1, element2 = (IOHIDElementRef)val2;
|
|
int usage1 = IOHIDElementGetUsage(element1), usage2 = IOHIDElementGetUsage(element2);
|
|
|
|
if (usage1 < usage2)
|
|
return kCFCompareLessThan;
|
|
if (usage1 > usage2)
|
|
return kCFCompareGreaterThan;
|
|
return kCFCompareEqualTo;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* driver_open
|
|
*/
|
|
LRESULT driver_open(LPSTR str, DWORD index)
|
|
{
|
|
if (index >= MAXJOYSTICK || joysticks[index].in_use)
|
|
return 0;
|
|
|
|
joysticks[index].in_use = TRUE;
|
|
return (LRESULT)&joysticks[index];
|
|
}
|
|
|
|
/**************************************************************************
|
|
* driver_close
|
|
*/
|
|
LRESULT driver_close(DWORD_PTR device_id)
|
|
{
|
|
joystick_t* joystick = joystick_from_id(device_id);
|
|
int i;
|
|
|
|
if (joystick == NULL)
|
|
return 0;
|
|
|
|
CFRelease(joystick->element);
|
|
for (i = 0; i < NUM_AXES; i++)
|
|
{
|
|
if (joystick->axes[i].element)
|
|
CFRelease(joystick->axes[i].element);
|
|
}
|
|
if (joystick->buttons)
|
|
CFRelease(joystick->buttons);
|
|
if (joystick->hatswitch)
|
|
CFRelease(joystick->hatswitch);
|
|
|
|
memset(joystick, 0, sizeof(*joystick));
|
|
return 1;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* open_joystick
|
|
*/
|
|
static BOOL open_joystick(joystick_t* joystick)
|
|
{
|
|
CFIndex index;
|
|
CFRange range;
|
|
|
|
if (joystick->element)
|
|
return TRUE;
|
|
|
|
if (!device_main_elements)
|
|
{
|
|
find_osx_devices();
|
|
if (!device_main_elements)
|
|
return FALSE;
|
|
}
|
|
|
|
index = joystick - joysticks;
|
|
if (index >= CFArrayGetCount(device_main_elements))
|
|
return FALSE;
|
|
|
|
joystick->element = (IOHIDElementRef)CFArrayGetValueAtIndex(device_main_elements, index);
|
|
joystick->buttons = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
collect_joystick_elements(joystick, joystick->element);
|
|
|
|
/* Sort buttons into correct order */
|
|
range.location = 0;
|
|
range.length = CFArrayGetCount(joystick->buttons);
|
|
CFArraySortValues(joystick->buttons, range, button_usage_comparator, NULL);
|
|
if (range.length > 32)
|
|
{
|
|
/* Delete any buttons beyond the first 32 */
|
|
range.location = 32;
|
|
range.length -= 32;
|
|
CFArrayReplaceValues(joystick->buttons, range, NULL, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* driver_joyGetDevCaps
|
|
*/
|
|
LRESULT driver_joyGetDevCaps(DWORD_PTR device_id, JOYCAPSW* caps, DWORD size)
|
|
{
|
|
joystick_t* joystick;
|
|
IOHIDDeviceRef device;
|
|
|
|
if ((joystick = joystick_from_id(device_id)) == NULL)
|
|
return MMSYSERR_NODRIVER;
|
|
|
|
if (!open_joystick(joystick))
|
|
return JOYERR_PARMS;
|
|
|
|
caps->szPname[0] = 0;
|
|
|
|
device = IOHIDElementGetDevice(joystick->element);
|
|
if (device)
|
|
{
|
|
CFStringRef product_name = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey));
|
|
if (product_name)
|
|
{
|
|
CFRange range;
|
|
|
|
range.location = 0;
|
|
range.length = min(MAXPNAMELEN - 1, CFStringGetLength(product_name));
|
|
CFStringGetCharacters(product_name, range, (UniChar*)caps->szPname);
|
|
caps->szPname[range.length] = 0;
|
|
}
|
|
}
|
|
|
|
caps->wMid = MM_MICROSOFT;
|
|
caps->wPid = MM_PC_JOYSTICK;
|
|
caps->wXmin = 0;
|
|
caps->wXmax = 0xFFFF;
|
|
caps->wYmin = 0;
|
|
caps->wYmax = 0xFFFF;
|
|
caps->wZmin = 0;
|
|
caps->wZmax = joystick->axes[AXIS_Z].element ? 0xFFFF : 0;
|
|
caps->wNumButtons = CFArrayGetCount(joystick->buttons);
|
|
if (size == sizeof(JOYCAPSW))
|
|
{
|
|
int i;
|
|
|
|
/* complete 95 structure */
|
|
caps->wRmin = 0;
|
|
caps->wRmax = 0xFFFF;
|
|
caps->wUmin = 0;
|
|
caps->wUmax = 0xFFFF;
|
|
caps->wVmin = 0;
|
|
caps->wVmax = 0xFFFF;
|
|
caps->wMaxAxes = 6; /* same as MS Joystick Driver */
|
|
caps->wNumAxes = 0;
|
|
caps->wMaxButtons = 32; /* same as MS Joystick Driver */
|
|
caps->szRegKey[0] = 0;
|
|
caps->szOEMVxD[0] = 0;
|
|
caps->wCaps = 0;
|
|
|
|
for (i = 0; i < NUM_AXES; i++)
|
|
{
|
|
if (joystick->axes[i].element)
|
|
{
|
|
caps->wNumAxes++;
|
|
switch (i)
|
|
{
|
|
case AXIS_Z: caps->wCaps |= JOYCAPS_HASZ; break;
|
|
case AXIS_RX: caps->wCaps |= JOYCAPS_HASV; break;
|
|
case AXIS_RY: caps->wCaps |= JOYCAPS_HASU; break;
|
|
case AXIS_RZ: caps->wCaps |= JOYCAPS_HASR; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (joystick->hatswitch)
|
|
caps->wCaps |= JOYCAPS_HASPOV | JOYCAPS_POV4DIR;
|
|
}
|
|
|
|
TRACE("name %s buttons %u axes %d caps 0x%08x\n", debugstr_w(caps->szPname), caps->wNumButtons, caps->wNumAxes, caps->wCaps);
|
|
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
/*
|
|
* Helper to get the value from an element
|
|
*/
|
|
static LRESULT driver_getElementValue(IOHIDDeviceRef device, IOHIDElementRef element, IOHIDValueRef *pValueRef)
|
|
{
|
|
IOReturn ret;
|
|
ret = IOHIDDeviceGetValue(device, element, pValueRef);
|
|
switch (ret)
|
|
{
|
|
case kIOReturnSuccess:
|
|
return JOYERR_NOERROR;
|
|
case kIOReturnNotAttached:
|
|
return JOYERR_UNPLUGGED;
|
|
default:
|
|
ERR("IOHIDDeviceGetValue returned 0x%x\n",ret);
|
|
return JOYERR_NOCANDO;
|
|
}
|
|
}
|
|
|
|
/**************************************************************************
|
|
* driver_joyGetPosEx
|
|
*/
|
|
LRESULT driver_joyGetPosEx(DWORD_PTR device_id, JOYINFOEX* info)
|
|
{
|
|
static const struct {
|
|
DWORD flag;
|
|
off_t offset;
|
|
} axis_map[NUM_AXES] = {
|
|
{ JOY_RETURNX, FIELD_OFFSET(JOYINFOEX, dwXpos) },
|
|
{ JOY_RETURNY, FIELD_OFFSET(JOYINFOEX, dwYpos) },
|
|
{ JOY_RETURNZ, FIELD_OFFSET(JOYINFOEX, dwZpos) },
|
|
{ JOY_RETURNV, FIELD_OFFSET(JOYINFOEX, dwVpos) },
|
|
{ JOY_RETURNU, FIELD_OFFSET(JOYINFOEX, dwUpos) },
|
|
{ JOY_RETURNR, FIELD_OFFSET(JOYINFOEX, dwRpos) },
|
|
};
|
|
|
|
joystick_t* joystick;
|
|
IOHIDDeviceRef device;
|
|
CFIndex i, count;
|
|
IOHIDValueRef valueRef;
|
|
long value;
|
|
LRESULT rc;
|
|
|
|
if ((joystick = joystick_from_id(device_id)) == NULL)
|
|
return MMSYSERR_NODRIVER;
|
|
|
|
if (!open_joystick(joystick))
|
|
return JOYERR_PARMS;
|
|
|
|
device = IOHIDElementGetDevice(joystick->element);
|
|
|
|
if (info->dwFlags & JOY_RETURNBUTTONS)
|
|
{
|
|
info->dwButtons = 0;
|
|
info->dwButtonNumber = 0;
|
|
|
|
count = CFArrayGetCount(joystick->buttons);
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
IOHIDElementRef button = (IOHIDElementRef)CFArrayGetValueAtIndex(joystick->buttons, i);
|
|
rc = driver_getElementValue(device, button, &valueRef);
|
|
if (rc != JOYERR_NOERROR)
|
|
return rc;
|
|
value = IOHIDValueGetIntegerValue(valueRef);
|
|
if (value)
|
|
{
|
|
info->dwButtons |= 1 << i;
|
|
info->dwButtonNumber++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NUM_AXES; i++)
|
|
{
|
|
if (info->dwFlags & axis_map[i].flag)
|
|
{
|
|
DWORD* field = (DWORD*)((char*)info + axis_map[i].offset);
|
|
if (joystick->axes[i].element)
|
|
{
|
|
rc = driver_getElementValue(device, joystick->axes[i].element, &valueRef);
|
|
if (rc != JOYERR_NOERROR)
|
|
return rc;
|
|
value = IOHIDValueGetIntegerValue(valueRef) - joystick->axes[i].min_value;
|
|
*field = MulDiv(value, 0xFFFF, joystick->axes[i].max_value - joystick->axes[i].min_value);
|
|
}
|
|
else
|
|
{
|
|
*field = 0;
|
|
info->dwFlags &= ~axis_map[i].flag;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (info->dwFlags & JOY_RETURNPOV)
|
|
{
|
|
if (joystick->hatswitch)
|
|
{
|
|
rc = driver_getElementValue(device, joystick->hatswitch, &valueRef);
|
|
if (rc != JOYERR_NOERROR)
|
|
return rc;
|
|
value = IOHIDValueGetIntegerValue(valueRef);
|
|
if (value >= 8)
|
|
info->dwPOV = JOY_POVCENTERED;
|
|
else
|
|
info->dwPOV = value * 4500;
|
|
}
|
|
else
|
|
{
|
|
info->dwPOV = JOY_POVCENTERED;
|
|
info->dwFlags &= ~JOY_RETURNPOV;
|
|
}
|
|
}
|
|
|
|
TRACE("x: %d, y: %d, z: %d, r: %d, u: %d, v: %d, buttons: 0x%04x, pov %d, flags: 0x%04x\n",
|
|
info->dwXpos, info->dwYpos, info->dwZpos, info->dwRpos, info->dwUpos, info->dwVpos, info->dwButtons, info->dwPOV, info->dwFlags);
|
|
|
|
return JOYERR_NOERROR;
|
|
}
|
|
|
|
/**************************************************************************
|
|
* driver_joyGetPos
|
|
*/
|
|
LRESULT driver_joyGetPos(DWORD_PTR device_id, JOYINFO* info)
|
|
{
|
|
JOYINFOEX ji;
|
|
LONG ret;
|
|
|
|
memset(&ji, 0, sizeof(ji));
|
|
|
|
ji.dwSize = sizeof(ji);
|
|
ji.dwFlags = JOY_RETURNX | JOY_RETURNY | JOY_RETURNZ | JOY_RETURNBUTTONS;
|
|
ret = driver_joyGetPosEx(device_id, &ji);
|
|
if (ret == JOYERR_NOERROR)
|
|
{
|
|
info->wXpos = ji.dwXpos;
|
|
info->wYpos = ji.dwYpos;
|
|
info->wZpos = ji.dwZpos;
|
|
info->wButtons = ji.dwButtons;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* HAVE_IOKIT_HID_IOHIDLIB_H */
|