wine/dlls/winejoystick.drv/joystick_osx.c
David Lawrie 4165674742 winejoystick.drv: Sort virtual joysticks by name on the Mac.
Signed-off-by: David Lawrie <david.dljunk@gmail.com>
Signed-off-by: Ken Thomases <ken@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
2016-07-25 14:22:23 +09:00

935 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 */