mirror of
git://source.winehq.org/git/wine.git
synced 2024-10-31 10:41:12 +00:00
1610 lines
54 KiB
C
1610 lines
54 KiB
C
/* DirectInput Joystick device for Mac OS/X
|
|
*
|
|
* Copyright 1998 Marcus Meissner
|
|
* Copyright 1998,1999 Lionel Ulmer
|
|
* Copyright 2000-2001 TransGaming Technologies Inc.
|
|
* Copyright 2009 CodeWeavers, Aric Stewart
|
|
*
|
|
* 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"
|
|
#include "wine/port.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>
|
|
#include <ForceFeedback/ForceFeedback.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
|
|
#endif /* HAVE_IOKIT_HID_IOHIDLIB_H */
|
|
|
|
#include "wine/debug.h"
|
|
#include "wine/unicode.h"
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winerror.h"
|
|
#include "winreg.h"
|
|
#include "dinput.h"
|
|
|
|
#include "dinput_private.h"
|
|
#include "device_private.h"
|
|
#include "joystick_private.h"
|
|
|
|
#ifdef HAVE_IOHIDMANAGERCREATE
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(dinput);
|
|
|
|
static CFMutableArrayRef device_main_elements = NULL;
|
|
|
|
typedef struct JoystickImpl JoystickImpl;
|
|
static const IDirectInputDevice8AVtbl JoystickAvt;
|
|
static const IDirectInputDevice8WVtbl JoystickWvt;
|
|
|
|
struct JoystickImpl
|
|
{
|
|
struct JoystickGenericImpl generic;
|
|
|
|
/* osx private */
|
|
int id;
|
|
CFArrayRef elements;
|
|
ObjProps **propmap;
|
|
FFDeviceObjectReference ff;
|
|
struct list effects;
|
|
};
|
|
|
|
static inline JoystickImpl *impl_from_IDirectInputDevice8A(IDirectInputDevice8A *iface)
|
|
{
|
|
return CONTAINING_RECORD(CONTAINING_RECORD(CONTAINING_RECORD(iface, IDirectInputDeviceImpl, IDirectInputDevice8A_iface),
|
|
JoystickGenericImpl, base), JoystickImpl, generic);
|
|
}
|
|
static inline JoystickImpl *impl_from_IDirectInputDevice8W(IDirectInputDevice8W *iface)
|
|
{
|
|
return CONTAINING_RECORD(CONTAINING_RECORD(CONTAINING_RECORD(iface, IDirectInputDeviceImpl, IDirectInputDevice8W_iface),
|
|
JoystickGenericImpl, base), JoystickImpl, generic);
|
|
}
|
|
|
|
typedef struct _EffectImpl {
|
|
IDirectInputEffect IDirectInputEffect_iface;
|
|
LONG ref;
|
|
|
|
JoystickImpl *device;
|
|
FFEffectObjectReference effect;
|
|
GUID guid;
|
|
|
|
struct list entry;
|
|
} EffectImpl;
|
|
|
|
static EffectImpl *impl_from_IDirectInputEffect(IDirectInputEffect *iface)
|
|
{
|
|
return CONTAINING_RECORD(iface, EffectImpl, IDirectInputEffect_iface);
|
|
}
|
|
|
|
static const IDirectInputEffectVtbl EffectVtbl;
|
|
|
|
static const GUID DInput_Wine_OsX_Joystick_GUID = { /* 59CAD8F6-E617-41E2-8EB7-47B23EEEDC5A */
|
|
0x59CAD8F6, 0xE617, 0x41E2, {0x8E, 0xB7, 0x47, 0xB2, 0x3E, 0xEE, 0xDC, 0x5A}
|
|
};
|
|
|
|
static HRESULT osx_to_win32_hresult(HRESULT in)
|
|
{
|
|
/* OSX returns 16-bit COM runtime errors, which we should
|
|
* convert to win32 */
|
|
switch(in){
|
|
case 0x80000001:
|
|
return E_NOTIMPL;
|
|
case 0x80000002:
|
|
return E_OUTOFMEMORY;
|
|
case 0x80000003:
|
|
return E_INVALIDARG;
|
|
case 0x80000004:
|
|
return E_NOINTERFACE;
|
|
case 0x80000005:
|
|
return E_POINTER;
|
|
case 0x80000006:
|
|
return E_HANDLE;
|
|
case 0x80000007:
|
|
return E_ABORT;
|
|
case 0x80000008:
|
|
return E_FAIL;
|
|
case 0x80000009:
|
|
return E_ACCESSDENIED;
|
|
case 0x8000FFFF:
|
|
return E_UNEXPECTED;
|
|
}
|
|
return in;
|
|
}
|
|
|
|
static void CFSetApplierFunctionCopyToCFArray(const void *value, void *context)
|
|
{
|
|
CFArrayAppendValue( ( CFMutableArrayRef ) context, value );
|
|
}
|
|
|
|
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 IOHIDDeviceRef get_device_ref(int id)
|
|
{
|
|
IOHIDElementRef device_main_element;
|
|
IOHIDDeviceRef hid_device;
|
|
|
|
TRACE("id %d\n", id);
|
|
|
|
if (!device_main_elements || id >= CFArrayGetCount(device_main_elements))
|
|
return 0;
|
|
|
|
device_main_element = (IOHIDElementRef)CFArrayGetValueAtIndex(device_main_elements, id);
|
|
if (!device_main_element)
|
|
{
|
|
ERR("Invalid Element requested %i\n",id);
|
|
return 0;
|
|
}
|
|
|
|
hid_device = IOHIDElementGetDevice(device_main_element);
|
|
if (!hid_device)
|
|
{
|
|
ERR("Invalid Device requested %i\n",id);
|
|
return 0;
|
|
}
|
|
|
|
TRACE("-> %s\n", debugstr_device(hid_device));
|
|
return hid_device;
|
|
}
|
|
|
|
static HRESULT get_ff(IOHIDDeviceRef device, FFDeviceObjectReference *ret)
|
|
{
|
|
io_service_t service;
|
|
CFMutableDictionaryRef matching;
|
|
CFTypeRef location_id;
|
|
HRESULT hr;
|
|
|
|
TRACE("device %s\n", debugstr_device(device));
|
|
|
|
matching = IOServiceMatching(kIOHIDDeviceKey);
|
|
if(!matching){
|
|
WARN("IOServiceMatching failed, force feedback disabled\n");
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
|
|
location_id = IOHIDDeviceGetProperty(device, CFSTR(kIOHIDLocationIDKey));
|
|
if(!location_id){
|
|
CFRelease(matching);
|
|
WARN("IOHIDDeviceGetProperty failed, force feedback disabled\n");
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
|
|
CFDictionaryAddValue(matching, CFSTR(kIOHIDLocationIDKey), location_id);
|
|
|
|
service = IOServiceGetMatchingService(kIOMasterPortDefault, matching);
|
|
|
|
if (ret)
|
|
hr = osx_to_win32_hresult(FFCreateDevice(service, ret));
|
|
else
|
|
hr = FFIsForceFeedback(service) == FF_OK ? S_OK : S_FALSE;
|
|
|
|
IOObjectRelease(service);
|
|
TRACE("-> hr 0x%08x *ret %p\n", hr, ret ? *ret : NULL);
|
|
return hr;
|
|
}
|
|
|
|
static CFMutableDictionaryRef create_osx_device_match(int usage)
|
|
{
|
|
CFMutableDictionaryRef result;
|
|
|
|
TRACE("usage %d\n", usage);
|
|
|
|
result = CFDictionaryCreateMutable( kCFAllocatorDefault, 0,
|
|
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
|
|
|
|
if ( result )
|
|
{
|
|
int number = kHIDPage_GenericDesktop;
|
|
CFNumberRef page = CFNumberCreate( kCFAllocatorDefault,
|
|
kCFNumberIntType, &number);
|
|
|
|
if (page)
|
|
{
|
|
CFNumberRef cf_usage;
|
|
|
|
CFDictionarySetValue( result, CFSTR( kIOHIDDeviceUsagePageKey ), page );
|
|
CFRelease( page );
|
|
|
|
cf_usage = CFNumberCreate( kCFAllocatorDefault,
|
|
kCFNumberIntType, &usage);
|
|
if (cf_usage)
|
|
{
|
|
CFDictionarySetValue( result, CFSTR( kIOHIDDeviceUsageKey ), cf_usage );
|
|
CFRelease( cf_usage );
|
|
}
|
|
else
|
|
{
|
|
ERR("CFNumberCreate() failed.\n");
|
|
CFRelease(result);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR("CFNumberCreate failed.\n");
|
|
CFRelease(result);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ERR("CFDictionaryCreateMutable failed.\n");
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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 idx, cnt = CFArrayGetCount(elements);
|
|
for (idx=0; idx<cnt; idx++)
|
|
{
|
|
IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(elements, idx);
|
|
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;
|
|
}
|
|
|
|
static void get_element_children(IOHIDElementRef element, CFMutableArrayRef all_children)
|
|
{
|
|
CFIndex idx, cnt;
|
|
CFArrayRef element_children = IOHIDElementGetChildren(element);
|
|
|
|
TRACE("element %s\n", debugstr_element(element));
|
|
|
|
cnt = CFArrayGetCount(element_children);
|
|
|
|
/* Either add the element to the array or grab its children */
|
|
for (idx=0; idx<cnt; idx++)
|
|
{
|
|
IOHIDElementRef child;
|
|
|
|
child = (IOHIDElementRef)CFArrayGetValueAtIndex(element_children, idx);
|
|
TRACE("child %s\n", debugstr_element(child));
|
|
if (IOHIDElementGetType(child) == kIOHIDElementTypeCollection)
|
|
get_element_children(child, all_children);
|
|
else
|
|
CFArrayAppendValue(all_children, child);
|
|
}
|
|
}
|
|
|
|
static int find_osx_devices(void)
|
|
{
|
|
IOHIDManagerRef hid_manager;
|
|
CFMutableDictionaryRef result;
|
|
CFSetRef devset;
|
|
CFMutableArrayRef matching;
|
|
|
|
TRACE("()\n");
|
|
|
|
hid_manager = IOHIDManagerCreate( kCFAllocatorDefault, 0L );
|
|
if (IOHIDManagerOpen( hid_manager, 0 ) != kIOReturnSuccess)
|
|
{
|
|
ERR("Couldn't open IOHIDManager.\n");
|
|
CFRelease( hid_manager );
|
|
return 0;
|
|
}
|
|
|
|
matching = CFArrayCreateMutable( kCFAllocatorDefault, 0,
|
|
&kCFTypeArrayCallBacks );
|
|
|
|
/* build matching dictionary */
|
|
result = create_osx_device_match(kHIDUsage_GD_Joystick);
|
|
if (!result)
|
|
{
|
|
CFRelease(matching);
|
|
goto fail;
|
|
}
|
|
CFArrayAppendValue( matching, result );
|
|
CFRelease( result );
|
|
result = create_osx_device_match(kHIDUsage_GD_GamePad);
|
|
if (!result)
|
|
{
|
|
CFRelease(matching);
|
|
goto fail;
|
|
}
|
|
CFArrayAppendValue( matching, result );
|
|
CFRelease( result );
|
|
|
|
IOHIDManagerSetDeviceMatchingMultiple( hid_manager, matching);
|
|
CFRelease( matching );
|
|
devset = IOHIDManagerCopyDevices( hid_manager );
|
|
if (devset)
|
|
{
|
|
CFIndex num_devices, num_main_elements, idx;
|
|
CFMutableArrayRef devices = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
CFSetApplyFunction(devset, CFSetApplierFunctionCopyToCFArray, devices);
|
|
CFRelease( devset);
|
|
num_devices = CFArrayGetCount(devices);
|
|
|
|
device_main_elements = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
if (!device_main_elements)
|
|
{
|
|
CFRelease( devices );
|
|
goto fail;
|
|
}
|
|
|
|
num_main_elements = 0;
|
|
for (idx = 0; idx < num_devices; idx++)
|
|
{
|
|
CFIndex top;
|
|
IOHIDDeviceRef hid_device;
|
|
|
|
hid_device = (IOHIDDeviceRef) CFArrayGetValueAtIndex(devices, idx);
|
|
TRACE("hid_device %s\n", debugstr_device(hid_device));
|
|
top = find_top_level(hid_device, device_main_elements);
|
|
num_main_elements += top;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static int get_osx_device_name(int id, char *name, int length)
|
|
{
|
|
CFStringRef str;
|
|
IOHIDDeviceRef hid_device;
|
|
|
|
hid_device = get_device_ref(id);
|
|
|
|
TRACE("id %d hid_device %s\n", id, debugstr_device(hid_device));
|
|
|
|
if (name)
|
|
name[0] = 0;
|
|
|
|
if (!hid_device)
|
|
return 0;
|
|
|
|
str = IOHIDDeviceGetProperty(hid_device, CFSTR( kIOHIDProductKey ));
|
|
if (str)
|
|
{
|
|
CFIndex len = CFStringGetLength(str);
|
|
if (length >= len)
|
|
{
|
|
CFStringGetCString(str,name,length,kCFStringEncodingASCII);
|
|
return len;
|
|
}
|
|
else
|
|
return (len+1);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void get_osx_device_elements(JoystickImpl *device, int axis_map[8])
|
|
{
|
|
IOHIDElementRef device_main_element;
|
|
CFMutableArrayRef elements;
|
|
DWORD sliders = 0;
|
|
|
|
TRACE("device %p device->id %d\n", device, device->id);
|
|
|
|
device->elements = NULL;
|
|
|
|
if (!device_main_elements || device->id >= CFArrayGetCount(device_main_elements))
|
|
return;
|
|
|
|
device_main_element = (IOHIDElementRef)CFArrayGetValueAtIndex(device_main_elements, device->id);
|
|
TRACE("device_main_element %s\n", debugstr_element(device_main_element));
|
|
if (!device_main_element)
|
|
return;
|
|
|
|
elements = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
get_element_children(device_main_element, elements);
|
|
|
|
if (elements)
|
|
{
|
|
CFIndex idx, cnt = CFArrayGetCount( elements );
|
|
CFMutableArrayRef axes = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
CFMutableArrayRef buttons = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
CFMutableArrayRef povs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
|
|
|
|
for ( idx = 0; idx < cnt; idx++ )
|
|
{
|
|
IOHIDElementRef element = ( IOHIDElementRef ) CFArrayGetValueAtIndex( elements, idx );
|
|
int type = IOHIDElementGetType( element );
|
|
|
|
TRACE("element %s\n", debugstr_element(element));
|
|
|
|
switch(type)
|
|
{
|
|
case kIOHIDElementTypeInput_Button:
|
|
{
|
|
int usage_page = IOHIDElementGetUsagePage( element );
|
|
TRACE("kIOHIDElementTypeInput_Button usage_page %d\n", usage_page);
|
|
if (usage_page != kHIDPage_Button)
|
|
{
|
|
/* avoid strange elements found on the 360 controller */
|
|
continue;
|
|
}
|
|
|
|
if (CFArrayGetCount(buttons) < 128)
|
|
CFArrayAppendValue(buttons, element);
|
|
break;
|
|
}
|
|
case kIOHIDElementTypeInput_Axis:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Axis\n");
|
|
CFArrayAppendValue(axes, element);
|
|
break;
|
|
}
|
|
case kIOHIDElementTypeInput_Misc:
|
|
{
|
|
uint32_t usage = IOHIDElementGetUsage( element );
|
|
switch(usage)
|
|
{
|
|
case kHIDUsage_GD_Hatswitch:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Hatswitch\n");
|
|
CFArrayAppendValue(povs, element);
|
|
break;
|
|
}
|
|
case kHIDUsage_GD_Slider:
|
|
sliders ++;
|
|
if (sliders > 2)
|
|
break;
|
|
/* fallthrough, sliders are axis */
|
|
case kHIDUsage_GD_X:
|
|
case kHIDUsage_GD_Y:
|
|
case kHIDUsage_GD_Z:
|
|
case kHIDUsage_GD_Rx:
|
|
case kHIDUsage_GD_Ry:
|
|
case kHIDUsage_GD_Rz:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_* (%d)\n", usage);
|
|
axis_map[CFArrayGetCount(axes)]=usage;
|
|
CFArrayAppendValue(axes, element);
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("kIOHIDElementTypeInput_Misc / Unhandled usage %i\n", usage);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("Unhandled type %i\n",type);
|
|
}
|
|
}
|
|
|
|
/* Sort buttons into correct order */
|
|
CFArraySortValues(buttons, CFRangeMake(0, CFArrayGetCount(buttons)), button_usage_comparator, NULL);
|
|
|
|
device->generic.devcaps.dwAxes = CFArrayGetCount(axes);
|
|
device->generic.devcaps.dwButtons = CFArrayGetCount(buttons);
|
|
device->generic.devcaps.dwPOVs = CFArrayGetCount(povs);
|
|
|
|
TRACE("axes %u povs %u buttons %u\n", device->generic.devcaps.dwAxes, device->generic.devcaps.dwPOVs,
|
|
device->generic.devcaps.dwButtons);
|
|
|
|
/* build our element array in the order that dinput expects */
|
|
CFArrayAppendArray(axes, povs, CFRangeMake(0, device->generic.devcaps.dwPOVs));
|
|
CFArrayAppendArray(axes, buttons, CFRangeMake(0, device->generic.devcaps.dwButtons));
|
|
device->elements = axes;
|
|
axes = NULL;
|
|
|
|
CFRelease(povs);
|
|
CFRelease(buttons);
|
|
CFRelease(elements);
|
|
}
|
|
else
|
|
{
|
|
device->generic.devcaps.dwAxes = 0;
|
|
device->generic.devcaps.dwButtons = 0;
|
|
device->generic.devcaps.dwPOVs = 0;
|
|
}
|
|
}
|
|
|
|
static void get_osx_device_elements_props(JoystickImpl *device)
|
|
{
|
|
TRACE("device %p\n", device);
|
|
|
|
if (device->elements)
|
|
{
|
|
CFIndex idx, cnt = CFArrayGetCount( device->elements );
|
|
|
|
for ( idx = 0; idx < cnt; idx++ )
|
|
{
|
|
IOHIDElementRef element = ( IOHIDElementRef ) CFArrayGetValueAtIndex( device->elements, idx );
|
|
|
|
TRACE("element %s\n", debugstr_element(element));
|
|
|
|
device->generic.props[idx].lDevMin = IOHIDElementGetLogicalMin(element);
|
|
device->generic.props[idx].lDevMax = IOHIDElementGetLogicalMax(element);
|
|
device->generic.props[idx].lMin = 0;
|
|
device->generic.props[idx].lMax = 0xffff;
|
|
device->generic.props[idx].lDeadZone = 0;
|
|
device->generic.props[idx].lSaturation = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void poll_osx_device_state(LPDIRECTINPUTDEVICE8A iface)
|
|
{
|
|
JoystickImpl *device = impl_from_IDirectInputDevice8A(iface);
|
|
IOHIDElementRef device_main_element;
|
|
IOHIDDeviceRef hid_device;
|
|
|
|
TRACE("device %p device->id %i\n", device, device->id);
|
|
|
|
if (!device_main_elements || device->id >= CFArrayGetCount(device_main_elements))
|
|
return;
|
|
|
|
device_main_element = (IOHIDElementRef) CFArrayGetValueAtIndex(device_main_elements, device->id);
|
|
hid_device = IOHIDElementGetDevice(device_main_element);
|
|
TRACE("main element %s hid_device %s\n", debugstr_element(device_main_element), debugstr_device(hid_device));
|
|
if (!hid_device)
|
|
return;
|
|
|
|
if (device->elements)
|
|
{
|
|
int button_idx = 0;
|
|
int pov_idx = 0;
|
|
int slider_idx = 0;
|
|
int inst_id;
|
|
CFIndex idx, cnt = CFArrayGetCount( device->elements );
|
|
|
|
for ( idx = 0; idx < cnt; idx++ )
|
|
{
|
|
IOHIDValueRef valueRef;
|
|
int val, oldVal, newVal;
|
|
IOHIDElementRef element = ( IOHIDElementRef ) CFArrayGetValueAtIndex( device->elements, idx );
|
|
int type = IOHIDElementGetType( element );
|
|
|
|
TRACE("element %s\n", debugstr_element(element));
|
|
|
|
switch(type)
|
|
{
|
|
case kIOHIDElementTypeInput_Button:
|
|
TRACE("kIOHIDElementTypeInput_Button\n");
|
|
if(button_idx < 128)
|
|
{
|
|
IOHIDDeviceGetValue(hid_device, element, &valueRef);
|
|
val = IOHIDValueGetIntegerValue(valueRef);
|
|
newVal = val ? 0x80 : 0x0;
|
|
oldVal = device->generic.js.rgbButtons[button_idx];
|
|
device->generic.js.rgbButtons[button_idx] = newVal;
|
|
TRACE("valueRef %s val %d oldVal %d newVal %d\n", debugstr_cf(valueRef), val, oldVal, newVal);
|
|
if (oldVal != newVal)
|
|
{
|
|
inst_id = DIDFT_MAKEINSTANCE(button_idx) | DIDFT_PSHBUTTON;
|
|
queue_event(iface,inst_id,newVal,GetCurrentTime(),device->generic.base.dinput->evsequence++);
|
|
}
|
|
button_idx ++;
|
|
}
|
|
break;
|
|
case kIOHIDElementTypeInput_Misc:
|
|
{
|
|
uint32_t usage = IOHIDElementGetUsage( element );
|
|
switch(usage)
|
|
{
|
|
case kHIDUsage_GD_Hatswitch:
|
|
{
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Hatswitch\n");
|
|
IOHIDDeviceGetValue(hid_device, element, &valueRef);
|
|
val = IOHIDValueGetIntegerValue(valueRef);
|
|
oldVal = device->generic.js.rgdwPOV[pov_idx];
|
|
if (val >= 8)
|
|
newVal = -1;
|
|
else
|
|
newVal = val * 4500;
|
|
device->generic.js.rgdwPOV[pov_idx] = newVal;
|
|
TRACE("valueRef %s val %d oldVal %d newVal %d\n", debugstr_cf(valueRef), val, oldVal, newVal);
|
|
if (oldVal != newVal)
|
|
{
|
|
inst_id = DIDFT_MAKEINSTANCE(pov_idx) | DIDFT_POV;
|
|
queue_event(iface,inst_id,newVal,GetCurrentTime(),device->generic.base.dinput->evsequence++);
|
|
}
|
|
pov_idx ++;
|
|
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:
|
|
case kHIDUsage_GD_Slider:
|
|
{
|
|
int wine_obj = -1;
|
|
|
|
IOHIDDeviceGetValue(hid_device, element, &valueRef);
|
|
val = IOHIDValueGetIntegerValue(valueRef);
|
|
newVal = joystick_map_axis(&device->generic.props[idx], val);
|
|
switch (usage)
|
|
{
|
|
case kHIDUsage_GD_X:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_X\n");
|
|
wine_obj = 0;
|
|
oldVal = device->generic.js.lX;
|
|
device->generic.js.lX = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Y:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Y\n");
|
|
wine_obj = 1;
|
|
oldVal = device->generic.js.lY;
|
|
device->generic.js.lY = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Z:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Z\n");
|
|
wine_obj = 2;
|
|
oldVal = device->generic.js.lZ;
|
|
device->generic.js.lZ = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Rx:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Rx\n");
|
|
wine_obj = 3;
|
|
oldVal = device->generic.js.lRx;
|
|
device->generic.js.lRx = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Ry:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Ry\n");
|
|
wine_obj = 4;
|
|
oldVal = device->generic.js.lRy;
|
|
device->generic.js.lRy = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Rz:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Rz\n");
|
|
wine_obj = 5;
|
|
oldVal = device->generic.js.lRz;
|
|
device->generic.js.lRz = newVal;
|
|
break;
|
|
case kHIDUsage_GD_Slider:
|
|
TRACE("kIOHIDElementTypeInput_Misc / kHIDUsage_GD_Slider\n");
|
|
wine_obj = 6 + slider_idx;
|
|
oldVal = device->generic.js.rglSlider[slider_idx];
|
|
device->generic.js.rglSlider[slider_idx] = newVal;
|
|
slider_idx ++;
|
|
break;
|
|
}
|
|
TRACE("valueRef %s val %d oldVal %d newVal %d\n", debugstr_cf(valueRef), val, oldVal, newVal);
|
|
if ((wine_obj != -1) &&
|
|
(oldVal != newVal))
|
|
{
|
|
inst_id = DIDFT_MAKEINSTANCE(wine_obj) | DIDFT_ABSAXIS;
|
|
queue_event(iface,inst_id,newVal,GetCurrentTime(),device->generic.base.dinput->evsequence++);
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("kIOHIDElementTypeInput_Misc / unhandled usage %i\n", usage);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
FIXME("Unhandled type %i\n",type);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static INT find_joystick_devices(void)
|
|
{
|
|
static INT joystick_devices_count = -1;
|
|
|
|
if (joystick_devices_count != -1) return joystick_devices_count;
|
|
|
|
joystick_devices_count = find_osx_devices();
|
|
|
|
return joystick_devices_count;
|
|
}
|
|
|
|
static HRESULT joydev_enum_deviceA(DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEA lpddi, DWORD version, int id)
|
|
{
|
|
TRACE("dwDevType %u dwFlags 0x%08x version 0x%04x id %d\n", dwDevType, dwFlags, version, id);
|
|
|
|
if (id >= find_joystick_devices()) return E_FAIL;
|
|
|
|
if ((dwDevType == 0) ||
|
|
((dwDevType == DIDEVTYPE_JOYSTICK) && (version > 0x0300 && version < 0x0800)) ||
|
|
(((dwDevType == DI8DEVCLASS_GAMECTRL) || (dwDevType == DI8DEVTYPE_JOYSTICK)) && (version >= 0x0800)))
|
|
{
|
|
if (dwFlags & DIEDFL_FORCEFEEDBACK) {
|
|
IOHIDDeviceRef device = get_device_ref(id);
|
|
if(!device)
|
|
return S_FALSE;
|
|
if(get_ff(device, NULL) != S_OK)
|
|
return S_FALSE;
|
|
}
|
|
/* Return joystick */
|
|
lpddi->guidInstance = DInput_Wine_OsX_Joystick_GUID;
|
|
lpddi->guidInstance.Data3 = id;
|
|
lpddi->guidProduct = DInput_Wine_OsX_Joystick_GUID;
|
|
/* we only support traditional joysticks for now */
|
|
if (version >= 0x0800)
|
|
lpddi->dwDevType = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_STANDARD << 8);
|
|
else
|
|
lpddi->dwDevType = DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8);
|
|
sprintf(lpddi->tszInstanceName, "Joystick %d", id);
|
|
|
|
/* get the device name */
|
|
get_osx_device_name(id, lpddi->tszProductName, MAX_PATH);
|
|
|
|
lpddi->guidFFDriver = GUID_NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static HRESULT joydev_enum_deviceW(DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEW lpddi, DWORD version, int id)
|
|
{
|
|
char name[MAX_PATH];
|
|
char friendly[32];
|
|
|
|
TRACE("dwDevType %u dwFlags 0x%08x version 0x%04x id %d\n", dwDevType, dwFlags, version, id);
|
|
|
|
if (id >= find_joystick_devices()) return E_FAIL;
|
|
|
|
if ((dwDevType == 0) ||
|
|
((dwDevType == DIDEVTYPE_JOYSTICK) && (version > 0x0300 && version < 0x0800)) ||
|
|
(((dwDevType == DI8DEVCLASS_GAMECTRL) || (dwDevType == DI8DEVTYPE_JOYSTICK)) && (version >= 0x0800))) {
|
|
if (dwFlags & DIEDFL_FORCEFEEDBACK) {
|
|
IOHIDDeviceRef device = get_device_ref(id);
|
|
if(!device)
|
|
return S_FALSE;
|
|
if(get_ff(device, NULL) != S_OK)
|
|
return S_FALSE;
|
|
}
|
|
/* Return joystick */
|
|
lpddi->guidInstance = DInput_Wine_OsX_Joystick_GUID;
|
|
lpddi->guidInstance.Data3 = id;
|
|
lpddi->guidProduct = DInput_Wine_OsX_Joystick_GUID;
|
|
/* we only support traditional joysticks for now */
|
|
if (version >= 0x0800)
|
|
lpddi->dwDevType = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_STANDARD << 8);
|
|
else
|
|
lpddi->dwDevType = DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8);
|
|
sprintf(friendly, "Joystick %d", id);
|
|
MultiByteToWideChar(CP_ACP, 0, friendly, -1, lpddi->tszInstanceName, MAX_PATH);
|
|
/* get the device name */
|
|
get_osx_device_name(id, name, MAX_PATH);
|
|
|
|
MultiByteToWideChar(CP_ACP, 0, name, -1, lpddi->tszProductName, MAX_PATH);
|
|
lpddi->guidFFDriver = GUID_NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
return S_FALSE;
|
|
}
|
|
|
|
static const char *osx_ff_axis_name(UInt8 axis)
|
|
{
|
|
static char ret[6];
|
|
switch(axis){
|
|
case FFJOFS_X:
|
|
return "FFJOFS_X";
|
|
case FFJOFS_Y:
|
|
return "FFJOFS_Y";
|
|
case FFJOFS_Z:
|
|
return "FFJOFS_Z";
|
|
}
|
|
sprintf(ret, "%u", (unsigned int)axis);
|
|
return ret;
|
|
}
|
|
|
|
static BOOL osx_axis_has_ff(FFCAPABILITIES *ffcaps, UInt8 axis)
|
|
{
|
|
int i;
|
|
for(i = 0; i < ffcaps->numFfAxes; ++i)
|
|
if(ffcaps->ffAxes[i] == axis)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static HRESULT alloc_device(REFGUID rguid, IDirectInputImpl *dinput,
|
|
JoystickImpl **pdev, unsigned short index)
|
|
{
|
|
DWORD i;
|
|
IOHIDDeviceRef device;
|
|
JoystickImpl* newDevice;
|
|
char name[MAX_PATH];
|
|
HRESULT hr;
|
|
LPDIDATAFORMAT df = NULL;
|
|
int idx = 0;
|
|
int axis_map[8]; /* max axes */
|
|
int slider_count = 0;
|
|
FFCAPABILITIES ffcaps;
|
|
|
|
TRACE("%s %p %p %hu\n", debugstr_guid(rguid), dinput, pdev, index);
|
|
|
|
newDevice = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(JoystickImpl));
|
|
if (newDevice == 0) {
|
|
WARN("out of memory\n");
|
|
*pdev = 0;
|
|
return DIERR_OUTOFMEMORY;
|
|
}
|
|
|
|
newDevice->id = index;
|
|
|
|
newDevice->generic.guidInstance = DInput_Wine_OsX_Joystick_GUID;
|
|
newDevice->generic.guidInstance.Data3 = index;
|
|
newDevice->generic.guidProduct = DInput_Wine_OsX_Joystick_GUID;
|
|
newDevice->generic.joy_polldev = poll_osx_device_state;
|
|
|
|
/* get the device name */
|
|
get_osx_device_name(index, name, MAX_PATH);
|
|
TRACE("Name %s\n",name);
|
|
|
|
/* copy the device name */
|
|
newDevice->generic.name = HeapAlloc(GetProcessHeap(),0,strlen(name) + 1);
|
|
strcpy(newDevice->generic.name, name);
|
|
|
|
list_init(&newDevice->effects);
|
|
device = get_device_ref(index);
|
|
if(get_ff(device, &newDevice->ff) == S_OK){
|
|
newDevice->generic.devcaps.dwFlags |= DIDC_FORCEFEEDBACK;
|
|
|
|
hr = FFDeviceGetForceFeedbackCapabilities(newDevice->ff, &ffcaps);
|
|
if(SUCCEEDED(hr)){
|
|
TRACE("FF Capabilities:\n");
|
|
TRACE("\tsupportedEffects: 0x%x\n", (unsigned int)ffcaps.supportedEffects);
|
|
TRACE("\temulatedEffects: 0x%x\n", (unsigned int)ffcaps.emulatedEffects);
|
|
TRACE("\tsubType: 0x%x\n", (unsigned int)ffcaps.subType);
|
|
TRACE("\tnumFfAxes: %u\n", (unsigned int)ffcaps.numFfAxes);
|
|
TRACE("\tffAxes: [");
|
|
for(i = 0; i < ffcaps.numFfAxes; ++i){
|
|
TRACE("%s", osx_ff_axis_name(ffcaps.ffAxes[i]));
|
|
if(i < ffcaps.numFfAxes - 1)
|
|
TRACE(", ");
|
|
}
|
|
TRACE("]\n");
|
|
TRACE("\tstorageCapacity: %u\n", (unsigned int)ffcaps.storageCapacity);
|
|
TRACE("\tplaybackCapacity: %u\n", (unsigned int)ffcaps.playbackCapacity);
|
|
}
|
|
|
|
hr = FFDeviceSendForceFeedbackCommand(newDevice->ff, FFSFFC_RESET);
|
|
if(FAILED(hr))
|
|
WARN("FFDeviceSendForceFeedbackCommand(FFSFFC_RESET) failed: %08x\n", hr);
|
|
|
|
hr = FFDeviceSendForceFeedbackCommand(newDevice->ff, FFSFFC_SETACTUATORSON);
|
|
if(FAILED(hr))
|
|
WARN("FFDeviceSendForceFeedbackCommand(FFSFFC_SETACTUATORSON) failed: %08x\n", hr);
|
|
}
|
|
|
|
memset(axis_map, 0, sizeof(axis_map));
|
|
get_osx_device_elements(newDevice, axis_map);
|
|
|
|
TRACE("%i axes %i buttons %i povs\n",newDevice->generic.devcaps.dwAxes,newDevice->generic.devcaps.dwButtons,newDevice->generic.devcaps.dwPOVs);
|
|
|
|
if (newDevice->generic.devcaps.dwButtons > 128)
|
|
{
|
|
WARN("Can't support %d buttons. Clamping down to 128\n", newDevice->generic.devcaps.dwButtons);
|
|
newDevice->generic.devcaps.dwButtons = 128;
|
|
}
|
|
|
|
newDevice->generic.base.IDirectInputDevice8A_iface.lpVtbl = &JoystickAvt;
|
|
newDevice->generic.base.IDirectInputDevice8W_iface.lpVtbl = &JoystickWvt;
|
|
newDevice->generic.base.ref = 1;
|
|
newDevice->generic.base.dinput = dinput;
|
|
newDevice->generic.base.guid = *rguid;
|
|
InitializeCriticalSection(&newDevice->generic.base.crit);
|
|
newDevice->generic.base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": JoystickImpl*->generic.base.crit");
|
|
|
|
/* Create copy of default data format */
|
|
if (!(df = HeapAlloc(GetProcessHeap(), 0, c_dfDIJoystick2.dwSize))) goto FAILED;
|
|
memcpy(df, &c_dfDIJoystick2, c_dfDIJoystick2.dwSize);
|
|
|
|
df->dwNumObjs = newDevice->generic.devcaps.dwAxes + newDevice->generic.devcaps.dwPOVs + newDevice->generic.devcaps.dwButtons;
|
|
if (!(df->rgodf = HeapAlloc(GetProcessHeap(), 0, df->dwNumObjs * df->dwObjSize))) goto FAILED;
|
|
|
|
for (i = 0; i < newDevice->generic.devcaps.dwAxes; i++)
|
|
{
|
|
int wine_obj = -1;
|
|
BOOL has_ff = FALSE;
|
|
switch (axis_map[i])
|
|
{
|
|
case kHIDUsage_GD_X:
|
|
wine_obj = 0;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_X);
|
|
break;
|
|
case kHIDUsage_GD_Y:
|
|
wine_obj = 1;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_Y);
|
|
break;
|
|
case kHIDUsage_GD_Z:
|
|
wine_obj = 2;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_Z);
|
|
break;
|
|
case kHIDUsage_GD_Rx:
|
|
wine_obj = 3;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_RX);
|
|
break;
|
|
case kHIDUsage_GD_Ry:
|
|
wine_obj = 4;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_RY);
|
|
break;
|
|
case kHIDUsage_GD_Rz:
|
|
wine_obj = 5;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_RZ);
|
|
break;
|
|
case kHIDUsage_GD_Slider:
|
|
wine_obj = 6 + slider_count;
|
|
has_ff = (newDevice->ff != 0) && osx_axis_has_ff(&ffcaps, FFJOFS_SLIDER(slider_count));
|
|
slider_count++;
|
|
break;
|
|
}
|
|
if (wine_obj < 0 ) continue;
|
|
|
|
memcpy(&df->rgodf[idx], &c_dfDIJoystick2.rgodf[wine_obj], df->dwObjSize);
|
|
df->rgodf[idx].dwType = DIDFT_MAKEINSTANCE(wine_obj) | DIDFT_ABSAXIS;
|
|
if(has_ff)
|
|
df->rgodf[idx].dwFlags |= DIDOI_FFACTUATOR;
|
|
++idx;
|
|
}
|
|
|
|
for (i = 0; i < newDevice->generic.devcaps.dwPOVs; i++)
|
|
{
|
|
memcpy(&df->rgodf[idx], &c_dfDIJoystick2.rgodf[i + 8], df->dwObjSize);
|
|
df->rgodf[idx++].dwType = DIDFT_MAKEINSTANCE(i) | DIDFT_POV;
|
|
}
|
|
|
|
for (i = 0; i < newDevice->generic.devcaps.dwButtons; i++)
|
|
{
|
|
memcpy(&df->rgodf[idx], &c_dfDIJoystick2.rgodf[i + 12], df->dwObjSize);
|
|
df->rgodf[idx ].pguid = &GUID_Button;
|
|
df->rgodf[idx++].dwType = DIDFT_MAKEINSTANCE(i) | DIDFT_PSHBUTTON;
|
|
}
|
|
newDevice->generic.base.data_format.wine_df = df;
|
|
|
|
/* initialize default properties */
|
|
get_osx_device_elements_props(newDevice);
|
|
|
|
IDirectInput_AddRef(&newDevice->generic.base.dinput->IDirectInput7A_iface);
|
|
|
|
EnterCriticalSection(&dinput->crit);
|
|
list_add_tail(&dinput->devices_list, &newDevice->generic.base.entry);
|
|
LeaveCriticalSection(&dinput->crit);
|
|
|
|
newDevice->generic.devcaps.dwSize = sizeof(newDevice->generic.devcaps);
|
|
newDevice->generic.devcaps.dwFlags |= DIDC_ATTACHED;
|
|
if (newDevice->generic.base.dinput->dwVersion >= 0x0800)
|
|
newDevice->generic.devcaps.dwDevType = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_STANDARD << 8);
|
|
else
|
|
newDevice->generic.devcaps.dwDevType = DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8);
|
|
newDevice->generic.devcaps.dwFFSamplePeriod = 0;
|
|
newDevice->generic.devcaps.dwFFMinTimeResolution = 0;
|
|
newDevice->generic.devcaps.dwFirmwareRevision = 0;
|
|
newDevice->generic.devcaps.dwHardwareRevision = 0;
|
|
newDevice->generic.devcaps.dwFFDriverVersion = 0;
|
|
|
|
if (TRACE_ON(dinput)) {
|
|
TRACE("allocated device %p\n", newDevice);
|
|
_dump_DIDATAFORMAT(newDevice->generic.base.data_format.wine_df);
|
|
_dump_DIDEVCAPS(&newDevice->generic.devcaps);
|
|
}
|
|
|
|
*pdev = newDevice;
|
|
|
|
return DI_OK;
|
|
|
|
FAILED:
|
|
hr = DIERR_OUTOFMEMORY;
|
|
if (newDevice->ff) FFReleaseDevice(newDevice->ff);
|
|
if (newDevice->elements) CFRelease(newDevice->elements);
|
|
if (df) HeapFree(GetProcessHeap(), 0, df->rgodf);
|
|
HeapFree(GetProcessHeap(), 0, df);
|
|
release_DataFormat(&newDevice->generic.base.data_format);
|
|
HeapFree(GetProcessHeap(),0,newDevice->generic.name);
|
|
HeapFree(GetProcessHeap(),0,newDevice);
|
|
*pdev = 0;
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************************************************************************
|
|
* get_joystick_index : Get the joystick index from a given GUID
|
|
*/
|
|
static unsigned short get_joystick_index(REFGUID guid)
|
|
{
|
|
GUID wine_joystick = DInput_Wine_OsX_Joystick_GUID;
|
|
GUID dev_guid = *guid;
|
|
|
|
wine_joystick.Data3 = 0;
|
|
dev_guid.Data3 = 0;
|
|
|
|
/* for the standard joystick GUID use index 0 */
|
|
if(IsEqualGUID(&GUID_Joystick,guid)) return 0;
|
|
|
|
/* for the wine joystick GUIDs use the index stored in Data3 */
|
|
if(IsEqualGUID(&wine_joystick, &dev_guid)) return guid->Data3;
|
|
|
|
return 0xffff;
|
|
}
|
|
|
|
static HRESULT joydev_create_device(IDirectInputImpl *dinput, REFGUID rguid, REFIID riid, LPVOID *pdev, int unicode)
|
|
{
|
|
unsigned short index;
|
|
int joystick_devices_count;
|
|
|
|
TRACE("%p %s %s %p %i\n", dinput, debugstr_guid(rguid), debugstr_guid(riid), pdev, unicode);
|
|
*pdev = NULL;
|
|
|
|
if ((joystick_devices_count = find_joystick_devices()) == 0)
|
|
return DIERR_DEVICENOTREG;
|
|
|
|
if ((index = get_joystick_index(rguid)) < 0xffff &&
|
|
joystick_devices_count && index < joystick_devices_count)
|
|
{
|
|
JoystickImpl *This;
|
|
HRESULT hr;
|
|
|
|
if (riid == NULL)
|
|
;/* nothing */
|
|
else if (IsEqualGUID(&IID_IDirectInputDeviceA, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice2A, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice7A, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice8A, riid))
|
|
{
|
|
unicode = 0;
|
|
}
|
|
else if (IsEqualGUID(&IID_IDirectInputDeviceW, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice2W, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice7W, riid) ||
|
|
IsEqualGUID(&IID_IDirectInputDevice8W, riid))
|
|
{
|
|
unicode = 1;
|
|
}
|
|
else
|
|
{
|
|
WARN("no interface\n");
|
|
return DIERR_NOINTERFACE;
|
|
}
|
|
|
|
hr = alloc_device(rguid, dinput, &This, index);
|
|
if (!This) return hr;
|
|
|
|
if (unicode)
|
|
*pdev = &This->generic.base.IDirectInputDevice8W_iface;
|
|
else
|
|
*pdev = &This->generic.base.IDirectInputDevice8A_iface;
|
|
return hr;
|
|
}
|
|
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
|
|
static HRESULT osx_set_autocenter(JoystickImpl *This,
|
|
const DIPROPDWORD *header)
|
|
{
|
|
UInt32 v;
|
|
HRESULT hr;
|
|
if(!This->ff)
|
|
return DIERR_UNSUPPORTED;
|
|
v = header->dwData;
|
|
hr = osx_to_win32_hresult(FFDeviceSetForceFeedbackProperty(This->ff, FFPROP_AUTOCENTER, &v));
|
|
TRACE("returning: %08x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT osx_set_ffgain(JoystickImpl *This, const DIPROPDWORD *header)
|
|
{
|
|
UInt32 v;
|
|
HRESULT hr;
|
|
if(!This->ff)
|
|
return DIERR_UNSUPPORTED;
|
|
v = header->dwData;
|
|
hr = osx_to_win32_hresult(FFDeviceSetForceFeedbackProperty(This->ff, FFPROP_FFGAIN, &v));
|
|
TRACE("returning: %08x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickWImpl_SetProperty(IDirectInputDevice8W *iface,
|
|
const GUID *prop, const DIPROPHEADER *header)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8W(iface);
|
|
|
|
TRACE("%p %s %p\n", This, debugstr_guid(prop), header);
|
|
|
|
switch(LOWORD(prop))
|
|
{
|
|
case (DWORD_PTR)DIPROP_AUTOCENTER:
|
|
return osx_set_autocenter(This, (const DIPROPDWORD *)header);
|
|
case (DWORD_PTR)DIPROP_FFGAIN:
|
|
return osx_set_ffgain(This, (const DIPROPDWORD *)header);
|
|
}
|
|
|
|
return JoystickWGenericImpl_SetProperty(iface, prop, header);
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickAImpl_SetProperty(IDirectInputDevice8A *iface,
|
|
const GUID *prop, const DIPROPHEADER *header)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8A(iface);
|
|
|
|
TRACE("%p %s %p\n", This, debugstr_guid(prop), header);
|
|
|
|
switch(LOWORD(prop))
|
|
{
|
|
case (DWORD_PTR)DIPROP_AUTOCENTER:
|
|
return osx_set_autocenter(This, (const DIPROPDWORD *)header);
|
|
case (DWORD_PTR)DIPROP_FFGAIN:
|
|
return osx_set_ffgain(This, (const DIPROPDWORD *)header);
|
|
}
|
|
|
|
return JoystickAGenericImpl_SetProperty(iface, prop, header);
|
|
}
|
|
|
|
static CFUUIDRef effect_win_to_mac(const GUID *effect)
|
|
{
|
|
#define DO_MAP(X) \
|
|
if(IsEqualGUID(&GUID_##X, effect)) \
|
|
return kFFEffectType_##X##_ID;
|
|
DO_MAP(ConstantForce)
|
|
DO_MAP(RampForce)
|
|
DO_MAP(Square)
|
|
DO_MAP(Sine)
|
|
DO_MAP(Triangle)
|
|
DO_MAP(SawtoothUp)
|
|
DO_MAP(SawtoothDown)
|
|
DO_MAP(Spring)
|
|
DO_MAP(Damper)
|
|
DO_MAP(Inertia)
|
|
DO_MAP(Friction)
|
|
DO_MAP(CustomForce)
|
|
#undef DO_MAP
|
|
WARN("Unknown effect GUID! %s\n", debugstr_guid(effect));
|
|
return 0;
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickWImpl_CreateEffect(IDirectInputDevice8W *iface,
|
|
const GUID *type, const DIEFFECT *params, IDirectInputEffect **out,
|
|
IUnknown *outer)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8W(iface);
|
|
EffectImpl *effect;
|
|
HRESULT hr;
|
|
|
|
TRACE("%p %s %p %p %p\n", iface, debugstr_guid(type), params, out, outer);
|
|
dump_DIEFFECT(params, type, 0);
|
|
|
|
if(!This->ff){
|
|
TRACE("No force feedback support\n");
|
|
*out = NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
if(outer)
|
|
WARN("aggregation not implemented\n");
|
|
|
|
effect = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*This));
|
|
effect->IDirectInputEffect_iface.lpVtbl = &EffectVtbl;
|
|
effect->ref = 1;
|
|
effect->guid = *type;
|
|
effect->device = This;
|
|
|
|
/* Mac's FFEFFECT and Win's DIEFFECT are binary identical. */
|
|
hr = osx_to_win32_hresult(FFDeviceCreateEffect(This->ff,
|
|
effect_win_to_mac(type), (FFEFFECT*)params, &effect->effect));
|
|
if(FAILED(hr)){
|
|
WARN("FFDeviceCreateEffect failed: %08x\n", hr);
|
|
HeapFree(GetProcessHeap(), 0, effect);
|
|
return hr;
|
|
}
|
|
|
|
list_add_tail(&This->effects, &effect->entry);
|
|
*out = &effect->IDirectInputEffect_iface;
|
|
|
|
TRACE("allocated effect: %p\n", effect);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickAImpl_CreateEffect(IDirectInputDevice8A *iface,
|
|
const GUID *type, const DIEFFECT *params, IDirectInputEffect **out,
|
|
IUnknown *outer)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8A(iface);
|
|
|
|
TRACE("%p %s %p %p %p\n", iface, debugstr_guid(type), params, out, outer);
|
|
|
|
return JoystickWImpl_CreateEffect(&This->generic.base.IDirectInputDevice8W_iface,
|
|
type, params, out, outer);
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickWImpl_SendForceFeedbackCommand(IDirectInputDevice8W *iface,
|
|
DWORD flags)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8W(iface);
|
|
HRESULT hr;
|
|
|
|
TRACE("%p 0x%x\n", This, flags);
|
|
|
|
if(!This->ff)
|
|
return DI_NOEFFECT;
|
|
|
|
hr = osx_to_win32_hresult(FFDeviceSendForceFeedbackCommand(This->ff, flags));
|
|
if(FAILED(hr)){
|
|
WARN("FFDeviceSendForceFeedbackCommand failed: %08x\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI JoystickAImpl_SendForceFeedbackCommand(IDirectInputDevice8A *iface,
|
|
DWORD flags)
|
|
{
|
|
JoystickImpl *This = impl_from_IDirectInputDevice8A(iface);
|
|
|
|
TRACE("%p 0x%x\n", This, flags);
|
|
|
|
return JoystickWImpl_SendForceFeedbackCommand(&This->generic.base.IDirectInputDevice8W_iface, flags);
|
|
}
|
|
|
|
const struct dinput_device joystick_osx_device = {
|
|
"Wine OS X joystick driver",
|
|
joydev_enum_deviceA,
|
|
joydev_enum_deviceW,
|
|
joydev_create_device
|
|
};
|
|
|
|
static const IDirectInputDevice8AVtbl JoystickAvt =
|
|
{
|
|
IDirectInputDevice2AImpl_QueryInterface,
|
|
IDirectInputDevice2AImpl_AddRef,
|
|
IDirectInputDevice2AImpl_Release,
|
|
JoystickAGenericImpl_GetCapabilities,
|
|
IDirectInputDevice2AImpl_EnumObjects,
|
|
JoystickAGenericImpl_GetProperty,
|
|
JoystickAImpl_SetProperty,
|
|
IDirectInputDevice2AImpl_Acquire,
|
|
IDirectInputDevice2AImpl_Unacquire,
|
|
JoystickAGenericImpl_GetDeviceState,
|
|
IDirectInputDevice2AImpl_GetDeviceData,
|
|
IDirectInputDevice2AImpl_SetDataFormat,
|
|
IDirectInputDevice2AImpl_SetEventNotification,
|
|
IDirectInputDevice2AImpl_SetCooperativeLevel,
|
|
JoystickAGenericImpl_GetObjectInfo,
|
|
JoystickAGenericImpl_GetDeviceInfo,
|
|
IDirectInputDevice2AImpl_RunControlPanel,
|
|
IDirectInputDevice2AImpl_Initialize,
|
|
JoystickAImpl_CreateEffect,
|
|
IDirectInputDevice2AImpl_EnumEffects,
|
|
IDirectInputDevice2AImpl_GetEffectInfo,
|
|
IDirectInputDevice2AImpl_GetForceFeedbackState,
|
|
JoystickAImpl_SendForceFeedbackCommand,
|
|
IDirectInputDevice2AImpl_EnumCreatedEffectObjects,
|
|
IDirectInputDevice2AImpl_Escape,
|
|
JoystickAGenericImpl_Poll,
|
|
IDirectInputDevice2AImpl_SendDeviceData,
|
|
IDirectInputDevice7AImpl_EnumEffectsInFile,
|
|
IDirectInputDevice7AImpl_WriteEffectToFile,
|
|
JoystickAGenericImpl_BuildActionMap,
|
|
JoystickAGenericImpl_SetActionMap,
|
|
IDirectInputDevice8AImpl_GetImageInfo
|
|
};
|
|
|
|
static const IDirectInputDevice8WVtbl JoystickWvt =
|
|
{
|
|
IDirectInputDevice2WImpl_QueryInterface,
|
|
IDirectInputDevice2WImpl_AddRef,
|
|
IDirectInputDevice2WImpl_Release,
|
|
JoystickWGenericImpl_GetCapabilities,
|
|
IDirectInputDevice2WImpl_EnumObjects,
|
|
JoystickWGenericImpl_GetProperty,
|
|
JoystickWImpl_SetProperty,
|
|
IDirectInputDevice2WImpl_Acquire,
|
|
IDirectInputDevice2WImpl_Unacquire,
|
|
JoystickWGenericImpl_GetDeviceState,
|
|
IDirectInputDevice2WImpl_GetDeviceData,
|
|
IDirectInputDevice2WImpl_SetDataFormat,
|
|
IDirectInputDevice2WImpl_SetEventNotification,
|
|
IDirectInputDevice2WImpl_SetCooperativeLevel,
|
|
JoystickWGenericImpl_GetObjectInfo,
|
|
JoystickWGenericImpl_GetDeviceInfo,
|
|
IDirectInputDevice2WImpl_RunControlPanel,
|
|
IDirectInputDevice2WImpl_Initialize,
|
|
JoystickWImpl_CreateEffect,
|
|
IDirectInputDevice2WImpl_EnumEffects,
|
|
IDirectInputDevice2WImpl_GetEffectInfo,
|
|
IDirectInputDevice2WImpl_GetForceFeedbackState,
|
|
JoystickWImpl_SendForceFeedbackCommand,
|
|
IDirectInputDevice2WImpl_EnumCreatedEffectObjects,
|
|
IDirectInputDevice2WImpl_Escape,
|
|
JoystickWGenericImpl_Poll,
|
|
IDirectInputDevice2WImpl_SendDeviceData,
|
|
IDirectInputDevice7WImpl_EnumEffectsInFile,
|
|
IDirectInputDevice7WImpl_WriteEffectToFile,
|
|
JoystickWGenericImpl_BuildActionMap,
|
|
JoystickWGenericImpl_SetActionMap,
|
|
IDirectInputDevice8WImpl_GetImageInfo
|
|
};
|
|
|
|
static HRESULT WINAPI effect_QueryInterface(IDirectInputEffect *iface,
|
|
const GUID *guid, void **out)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
|
|
TRACE("%p %s %p\n", This, debugstr_guid(guid), out);
|
|
|
|
if(IsEqualIID(guid, &IID_IDirectInputEffect)){
|
|
*out = iface;
|
|
IDirectInputEffect_AddRef(iface);
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI effect_AddRef(IDirectInputEffect *iface)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
ULONG ref = InterlockedIncrement(&This->ref);
|
|
TRACE("%p, ref is now: %u\n", This, ref);
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI effect_Release(IDirectInputEffect *iface)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
ULONG ref = InterlockedDecrement(&This->ref);
|
|
TRACE("%p, ref is now: %u\n", This, ref);
|
|
|
|
if(!ref){
|
|
list_remove(&This->entry);
|
|
FFDeviceReleaseEffect(This->device->ff, This->effect);
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Initialize(IDirectInputEffect *iface, HINSTANCE hinst,
|
|
DWORD version, const GUID *guid)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p 0x%x, %s\n", This, hinst, version, debugstr_guid(guid));
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_GetEffectGuid(IDirectInputEffect *iface, GUID *out)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p\n", This, out);
|
|
*out = This->guid;
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_GetParameters(IDirectInputEffect *iface,
|
|
DIEFFECT *effect, DWORD flags)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p 0x%x\n", This, effect, flags);
|
|
return osx_to_win32_hresult(FFEffectGetParameters(This->effect, (FFEFFECT*)effect, flags));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_SetParameters(IDirectInputEffect *iface,
|
|
const DIEFFECT *effect, DWORD flags)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p 0x%x\n", This, effect, flags);
|
|
dump_DIEFFECT(effect, &This->guid, flags);
|
|
return osx_to_win32_hresult(FFEffectSetParameters(This->effect, (FFEFFECT*)effect, flags));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Start(IDirectInputEffect *iface, DWORD iterations,
|
|
DWORD flags)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p 0x%x 0x%x\n", This, iterations, flags);
|
|
return osx_to_win32_hresult(FFEffectStart(This->effect, iterations, flags));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Stop(IDirectInputEffect *iface)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p\n", This);
|
|
return osx_to_win32_hresult(FFEffectStop(This->effect));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_GetEffectStatus(IDirectInputEffect *iface, DWORD *flags)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p\n", This, flags);
|
|
return osx_to_win32_hresult(FFEffectGetEffectStatus(This->effect, (UInt32*)flags));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Download(IDirectInputEffect *iface)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p\n", This);
|
|
return osx_to_win32_hresult(FFEffectDownload(This->effect));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Unload(IDirectInputEffect *iface)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p\n", This);
|
|
return osx_to_win32_hresult(FFEffectUnload(This->effect));
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Escape(IDirectInputEffect *iface, DIEFFESCAPE *escape)
|
|
{
|
|
EffectImpl *This = impl_from_IDirectInputEffect(iface);
|
|
TRACE("%p %p\n", This, escape);
|
|
return osx_to_win32_hresult(FFEffectEscape(This->effect, (FFEFFESCAPE*)escape));
|
|
}
|
|
|
|
static const IDirectInputEffectVtbl EffectVtbl = {
|
|
effect_QueryInterface,
|
|
effect_AddRef,
|
|
effect_Release,
|
|
effect_Initialize,
|
|
effect_GetEffectGuid,
|
|
effect_GetParameters,
|
|
effect_SetParameters,
|
|
effect_Start,
|
|
effect_Stop,
|
|
effect_GetEffectStatus,
|
|
effect_Download,
|
|
effect_Unload,
|
|
effect_Escape
|
|
};
|
|
|
|
#else /* HAVE_IOHIDMANAGERCREATE */
|
|
|
|
const struct dinput_device joystick_osx_device = {
|
|
"Wine OS X joystick driver",
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
#endif /* HAVE_IOHIDMANAGERCREATE */
|