blob: 48c05a05ef0acfdc226173638c44a8b982b85e8f [file] [log] [blame]
/*
* 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.
*
* 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,
AXIS_Y,
AXIS_Z,
AXIS_RX,
AXIS_RY,
AXIS_RZ,
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 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>", device,
debugstr_cf(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey))));
}
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(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;
}
/**************************************************************************
* 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))
{
CFArrayAppendValue(main_elements, element);
total++;
}
}
}
CFRelease(elements);
}
TRACE("-> total %d\n", (int)total);
return total;
}
/**************************************************************************
* find_osx_devices
*/
static int find_osx_devices(void)
{
IOHIDManagerRef hid_manager;
int usages[] = { kHIDUsage_GD_Joystick, kHIDUsage_GD_GamePad };
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;
const void** refs;
CFArrayRef devices;
num_devices = CFSetGetCount(devset);
refs = HeapAlloc(GetProcessHeap(), 0, num_devices * sizeof(*refs));
if (!refs)
{
CFRelease(devset);
goto fail;
}
CFSetGetValues(devset, refs);
devices = CFArrayCreate(NULL, refs, num_devices, &kCFTypeArrayCallBacks);
HeapFree(GetProcessHeap(), 0, refs);
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;
child = (IOHIDElementRef)CFArrayGetValueAtIndex(children, i);
TRACE("child %s\n", debugstr_element(child));
type = IOHIDElementGetType(child);
switch (type)
{
case kIOHIDElementTypeCollection:
collect_joystick_elements(joystick, child);
break;
case kIOHIDElementTypeInput_Button:
{
int usage_page = IOHIDElementGetUsagePage(child);
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:
{
TRACE("kIOHIDElementTypeInput_Axis; ignoring\n");
break;
}
case kIOHIDElementTypeInput_Misc:
{
uint32_t usage = IOHIDElementGetUsage( child );
switch(usage)
{
case kHIDUsage_GD_Hatswitch:
{
TRACE("kIOHIDElementTypeInput_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(usage);
TRACE("kIOHIDElementTypeInput_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:
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Slider; ignoring\n");
break;
default:
FIXME("kIOHIDElementTypeInput_Misc / Unhandled usage %d\n", usage);
break;
}
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_HASU; break;
case AXIS_RY: caps->wCaps |= JOYCAPS_HASV; 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_RETURNU, FIELD_OFFSET(JOYINFOEX, dwUpos) },
{ JOY_RETURNV, FIELD_OFFSET(JOYINFOEX, dwVpos) },
{ 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 */