/* * 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 #include #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 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("", device, debugstr_cf(IOHIDDeviceGetProperty(device, CFSTR(kIOHIDProductKey)))); } static const char* debugstr_element(IOHIDElementRef element) { return wine_dbg_sprintf("", 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; } /************************************************************************** * 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; 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; 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_ (%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_ (%d)\n ignoring\n", usage); else { TRACE("kIOHIDElementTypeInput_Axis/Misc / kHIDUsage_GD_ (%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_ (%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 */