2148 lines
84 KiB
C
2148 lines
84 KiB
C
/* DirectInput HID Joystick device
|
|
*
|
|
* Copyright 2021 Rémi Bernon for CodeWeavers
|
|
*
|
|
* 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 <assert.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include "ntstatus.h"
|
|
#define WIN32_NO_STATUS
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winternl.h"
|
|
#include "winuser.h"
|
|
#include "winerror.h"
|
|
#include "winreg.h"
|
|
|
|
#include "ddk/hidsdi.h"
|
|
#include "setupapi.h"
|
|
#include "devguid.h"
|
|
#include "dinput.h"
|
|
#include "setupapi.h"
|
|
|
|
#include "wine/debug.h"
|
|
#include "wine/hid.h"
|
|
|
|
#include "dinput_private.h"
|
|
#include "device_private.h"
|
|
#include "joystick_private.h"
|
|
|
|
#include "initguid.h"
|
|
#include "devpkey.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(dinput);
|
|
|
|
DEFINE_GUID( GUID_DEVINTERFACE_WINEXINPUT,0x6c53d5fd,0x6480,0x440f,0xb6,0x18,0x47,0x67,0x50,0xc5,0xe1,0xa6 );
|
|
DEFINE_GUID( hid_joystick_guid, 0x9e573edb, 0x7734, 0x11d2, 0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf7 );
|
|
DEFINE_DEVPROPKEY( DEVPROPKEY_HID_HANDLE, 0xbc62e415, 0xf4fe, 0x405c, 0x8e, 0xda, 0x63, 0x6f, 0xb5, 0x9f, 0x08, 0x98, 2 );
|
|
|
|
static inline const char *debugstr_hid_value_caps( struct hid_value_caps *caps )
|
|
{
|
|
if (!caps) return "(null)";
|
|
return wine_dbg_sprintf( "RId %d, Usg %02x:%02x-%02x Dat %02x-%02x, Str %d-%d, Des %d-%d, "
|
|
"Bits %02x Flags %#x, LCol %d LUsg %02x:%02x, BitSz %d, RCnt %d, Unit %x E%+d, Log %+d-%+d, Phy %+d-%+d",
|
|
caps->report_id, caps->usage_page, caps->usage_min, caps->usage_max, caps->data_index_min, caps->data_index_max,
|
|
caps->string_min, caps->string_max, caps->designator_min, caps->designator_max, caps->bit_field, caps->flags,
|
|
caps->link_collection, caps->link_usage_page, caps->link_usage, caps->bit_size, caps->report_count,
|
|
caps->units, caps->units_exp, caps->logical_min, caps->logical_max, caps->physical_min, caps->physical_max );
|
|
}
|
|
|
|
static inline const char *debugstr_hid_collection_node( struct hid_collection_node *node )
|
|
{
|
|
if (!node) return "(null)";
|
|
return wine_dbg_sprintf( "Usg %02x:%02x, Parent %u, Next %u, NbChild %u, Child %u, Type %02x",
|
|
node->usage_page, node->usage, node->parent, node->next_sibling,
|
|
node->number_of_children, node->first_child, node->collection_type );
|
|
}
|
|
|
|
struct extra_caps
|
|
{
|
|
LONG deadzone;
|
|
LONG saturation;
|
|
};
|
|
|
|
struct pid_control_report
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG control_coll;
|
|
};
|
|
|
|
struct pid_effect_update
|
|
{
|
|
BYTE id;
|
|
ULONG collection;
|
|
ULONG type_coll;
|
|
ULONG axes_coll;
|
|
ULONG axis_count;
|
|
struct hid_value_caps *axis_caps[6];
|
|
struct hid_value_caps *duration_caps;
|
|
struct hid_value_caps *gain_caps;
|
|
struct hid_value_caps *sample_period_caps;
|
|
struct hid_value_caps *start_delay_caps;
|
|
struct hid_value_caps *trigger_button_caps;
|
|
struct hid_value_caps *trigger_repeat_interval_caps;
|
|
};
|
|
|
|
#define DEVICE_STATE_MAX_SIZE 1024
|
|
|
|
struct hid_joystick
|
|
{
|
|
IDirectInputDeviceImpl base;
|
|
LONG ref;
|
|
|
|
HANDLE device;
|
|
OVERLAPPED read_ovl;
|
|
PHIDP_PREPARSED_DATA preparsed;
|
|
|
|
DIDEVICEINSTANCEW instance;
|
|
WCHAR device_path[MAX_PATH];
|
|
HIDD_ATTRIBUTES attrs;
|
|
DIDEVCAPS dev_caps;
|
|
HIDP_CAPS caps;
|
|
|
|
struct extra_caps *input_extra_caps;
|
|
|
|
char *input_report_buf;
|
|
char *output_report_buf;
|
|
USAGE_AND_PAGE *usages_buf;
|
|
ULONG usages_count;
|
|
|
|
BYTE device_state_report_id;
|
|
BYTE device_state[DEVICE_STATE_MAX_SIZE];
|
|
|
|
struct list effect_list;
|
|
struct pid_control_report pid_device_control;
|
|
struct pid_control_report pid_effect_control;
|
|
struct pid_effect_update pid_effect_update;
|
|
};
|
|
|
|
static inline struct hid_joystick *impl_from_IDirectInputDevice8W( IDirectInputDevice8W *iface )
|
|
{
|
|
return CONTAINING_RECORD( CONTAINING_RECORD( iface, IDirectInputDeviceImpl, IDirectInputDevice8W_iface ),
|
|
struct hid_joystick, base );
|
|
}
|
|
|
|
struct hid_joystick_effect
|
|
{
|
|
IDirectInputEffect IDirectInputEffect_iface;
|
|
LONG ref;
|
|
|
|
struct list entry;
|
|
struct hid_joystick *joystick;
|
|
};
|
|
|
|
static inline struct hid_joystick_effect *impl_from_IDirectInputEffect( IDirectInputEffect *iface )
|
|
{
|
|
return CONTAINING_RECORD( iface, struct hid_joystick_effect, IDirectInputEffect_iface );
|
|
}
|
|
|
|
static const GUID *object_usage_to_guid( USAGE usage_page, USAGE usage )
|
|
{
|
|
switch (usage_page)
|
|
{
|
|
case HID_USAGE_PAGE_BUTTON: return &GUID_Button;
|
|
case HID_USAGE_PAGE_GENERIC:
|
|
switch (usage)
|
|
{
|
|
case HID_USAGE_GENERIC_X: return &GUID_XAxis;
|
|
case HID_USAGE_GENERIC_Y: return &GUID_YAxis;
|
|
case HID_USAGE_GENERIC_Z: return &GUID_ZAxis;
|
|
case HID_USAGE_GENERIC_WHEEL: return &GUID_ZAxis;
|
|
case HID_USAGE_GENERIC_RX: return &GUID_RxAxis;
|
|
case HID_USAGE_GENERIC_RY: return &GUID_RyAxis;
|
|
case HID_USAGE_GENERIC_RZ: return &GUID_RzAxis;
|
|
case HID_USAGE_GENERIC_SLIDER: return &GUID_Slider;
|
|
case HID_USAGE_GENERIC_HATSWITCH: return &GUID_POV;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return &GUID_Unknown;
|
|
}
|
|
|
|
static inline USAGE effect_guid_to_usage( const GUID *guid )
|
|
{
|
|
if (IsEqualGUID( guid, &GUID_CustomForce )) return PID_USAGE_ET_CUSTOM_FORCE_DATA;
|
|
if (IsEqualGUID( guid, &GUID_ConstantForce )) return PID_USAGE_ET_CONSTANT_FORCE;
|
|
if (IsEqualGUID( guid, &GUID_RampForce )) return PID_USAGE_ET_RAMP;
|
|
if (IsEqualGUID( guid, &GUID_Square )) return PID_USAGE_ET_SQUARE;
|
|
if (IsEqualGUID( guid, &GUID_Sine )) return PID_USAGE_ET_SINE;
|
|
if (IsEqualGUID( guid, &GUID_Triangle )) return PID_USAGE_ET_TRIANGLE;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothUp )) return PID_USAGE_ET_SAWTOOTH_UP;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothDown )) return PID_USAGE_ET_SAWTOOTH_DOWN;
|
|
if (IsEqualGUID( guid, &GUID_Spring )) return PID_USAGE_ET_SPRING;
|
|
if (IsEqualGUID( guid, &GUID_Damper )) return PID_USAGE_ET_DAMPER;
|
|
if (IsEqualGUID( guid, &GUID_Inertia )) return PID_USAGE_ET_INERTIA;
|
|
if (IsEqualGUID( guid, &GUID_Friction )) return PID_USAGE_ET_FRICTION;
|
|
return 0;
|
|
}
|
|
|
|
static const WCHAR *effect_guid_to_string( const GUID *guid )
|
|
{
|
|
static const WCHAR guid_customforce_w[] = {'G','U','I','D','_','C','u','s','t','o','m','F','o','r','c','e',0};
|
|
static const WCHAR guid_constantforce_w[] = {'G','U','I','D','_','C','o','n','s','t','a','n','t','F','o','r','c','e',0};
|
|
static const WCHAR guid_rampforce_w[] = {'G','U','I','D','_','R','a','m','p','F','o','r','c','e',0};
|
|
static const WCHAR guid_square_w[] = {'G','U','I','D','_','S','q','u','a','r','e',0};
|
|
static const WCHAR guid_sine_w[] = {'G','U','I','D','_','S','i','n','e',0};
|
|
static const WCHAR guid_triangle_w[] = {'G','U','I','D','_','T','r','i','a','n','g','l','e',0};
|
|
static const WCHAR guid_sawtoothup_w[] = {'G','U','I','D','_','S','a','w','t','o','o','t','h','U','p',0};
|
|
static const WCHAR guid_sawtoothdown_w[] = {'G','U','I','D','_','S','a','w','t','o','o','t','h','D','o','w','n',0};
|
|
static const WCHAR guid_spring_w[] = {'G','U','I','D','_','S','p','r','i','n','g',0};
|
|
static const WCHAR guid_damper_w[] = {'G','U','I','D','_','D','a','m','p','e','r',0};
|
|
static const WCHAR guid_inertia_w[] = {'G','U','I','D','_','I','n','e','r','t','i','a',0};
|
|
static const WCHAR guid_friction_w[] = {'G','U','I','D','_','F','r','i','c','t','i','o','n',0};
|
|
if (IsEqualGUID( guid, &GUID_CustomForce )) return guid_customforce_w;
|
|
if (IsEqualGUID( guid, &GUID_ConstantForce )) return guid_constantforce_w;
|
|
if (IsEqualGUID( guid, &GUID_RampForce )) return guid_rampforce_w;
|
|
if (IsEqualGUID( guid, &GUID_Square )) return guid_square_w;
|
|
if (IsEqualGUID( guid, &GUID_Sine )) return guid_sine_w;
|
|
if (IsEqualGUID( guid, &GUID_Triangle )) return guid_triangle_w;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothUp )) return guid_sawtoothup_w;
|
|
if (IsEqualGUID( guid, &GUID_SawtoothDown )) return guid_sawtoothdown_w;
|
|
if (IsEqualGUID( guid, &GUID_Spring )) return guid_spring_w;
|
|
if (IsEqualGUID( guid, &GUID_Damper )) return guid_damper_w;
|
|
if (IsEqualGUID( guid, &GUID_Inertia )) return guid_inertia_w;
|
|
if (IsEqualGUID( guid, &GUID_Friction )) return guid_friction_w;
|
|
return NULL;
|
|
}
|
|
|
|
typedef BOOL (*enum_object_callback)( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data );
|
|
|
|
static BOOL enum_object( struct hid_joystick *impl, const DIPROPHEADER *filter, DWORD flags,
|
|
enum_object_callback callback, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
if (flags != DIDFT_ALL && !(flags & DIDFT_GETTYPE( instance->dwType ))) return DIENUM_CONTINUE;
|
|
|
|
switch (filter->dwHow)
|
|
{
|
|
case DIPH_DEVICE:
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYOFFSET:
|
|
if (filter->dwObj != instance->dwOfs) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYID:
|
|
if ((filter->dwObj & 0x00ffffff) != (instance->dwType & 0x00ffffff)) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
case DIPH_BYUSAGE:
|
|
if (HIWORD( filter->dwObj ) != instance->wUsagePage) return DIENUM_CONTINUE;
|
|
if (LOWORD( filter->dwObj ) != instance->wUsage) return DIENUM_CONTINUE;
|
|
return callback( impl, caps, instance, data );
|
|
default:
|
|
FIXME( "unimplemented filter dwHow %#x dwObj %#x\n", filter->dwHow, filter->dwObj );
|
|
break;
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static void check_pid_effect_axis_caps( struct hid_joystick *impl, DIDEVICEOBJECTINSTANCEW *instance )
|
|
{
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
ULONG i;
|
|
|
|
for (i = 0; i < effect_update->axis_count; ++i)
|
|
{
|
|
if (effect_update->axis_caps[i]->usage_page != instance->wUsagePage) continue;
|
|
if (effect_update->axis_caps[i]->usage_min > instance->wUsage) continue;
|
|
if (effect_update->axis_caps[i]->usage_max >= instance->wUsage) break;
|
|
}
|
|
|
|
if (i == effect_update->axis_count) return;
|
|
instance->dwType |= DIDFT_FFACTUATOR;
|
|
instance->dwFlags |= DIDOI_FFACTUATOR;
|
|
}
|
|
|
|
static void set_axis_type( DIDEVICEOBJECTINSTANCEW *instance, BOOL *seen, DWORD i, DWORD *count )
|
|
{
|
|
if (!seen[i]) instance->dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( i );
|
|
else instance->dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 6 + *count++ );
|
|
seen[i] = TRUE;
|
|
}
|
|
|
|
static BOOL enum_objects( struct hid_joystick *impl, const DIPROPHEADER *header, DWORD flags,
|
|
enum_object_callback callback, void *data )
|
|
{
|
|
DWORD collection = 0, object = 0, axis = 0, button = 0, pov = 0, value_ofs = 0, button_ofs = 0, i, j;
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
DIDEVICEOBJECTINSTANCEW instance = {.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW)};
|
|
struct hid_value_caps *caps, *caps_end, *nary, *nary_end, *effect_caps;
|
|
DIDATAFORMAT *format = impl->base.data_format.wine_df;
|
|
int *offsets = impl->base.data_format.offsets;
|
|
struct hid_collection_node *node, *node_end;
|
|
DIPROPHEADER filter = *header;
|
|
BOOL ret, seen_axis[6] = {0};
|
|
|
|
if (filter.dwHow == DIPH_BYOFFSET)
|
|
{
|
|
if (!offsets) return DIENUM_CONTINUE;
|
|
for (i = 0; i < format->dwNumObjs; ++i)
|
|
if (offsets[i] == filter.dwObj) break;
|
|
if (i == format->dwNumObjs) return DIENUM_CONTINUE;
|
|
filter.dwObj = format->rgodf[i].dwOfs;
|
|
}
|
|
|
|
button_ofs += impl->caps.NumberInputValueCaps * sizeof(LONG);
|
|
button_ofs += impl->caps.NumberOutputValueCaps * sizeof(LONG);
|
|
button_ofs += impl->caps.NumberFeatureValueCaps * sizeof(LONG);
|
|
|
|
for (caps = HID_INPUT_VALUE_CAPS( preparsed ), caps_end = caps + preparsed->input_caps_count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) continue;
|
|
|
|
if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
{
|
|
TRACE( "Ignoring input value %s, vendor specific.\n", debugstr_hid_value_caps( caps ) );
|
|
value_ofs += (caps->usage_max - caps->usage_min + 1) * sizeof(LONG);
|
|
}
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
instance.dwOfs = value_ofs;
|
|
switch (MAKELONG(j, caps->usage_page))
|
|
{
|
|
case MAKELONG(HID_USAGE_GENERIC_X, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_Y, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_Z, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RX, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RY, HID_USAGE_PAGE_GENERIC):
|
|
case MAKELONG(HID_USAGE_GENERIC_RZ, HID_USAGE_PAGE_GENERIC):
|
|
set_axis_type( &instance, seen_axis, j - HID_USAGE_GENERIC_X, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_GENERIC_WHEEL, HID_USAGE_PAGE_GENERIC):
|
|
set_axis_type( &instance, seen_axis, 2, &axis );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
case MAKELONG(HID_USAGE_GENERIC_HATSWITCH, HID_USAGE_PAGE_GENERIC):
|
|
instance.dwType = DIDFT_POV | DIDFT_MAKEINSTANCE( pov++ );
|
|
instance.dwFlags = 0;
|
|
break;
|
|
default:
|
|
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( 6 + axis++ );
|
|
instance.dwFlags = DIDOI_ASPECTPOSITION;
|
|
break;
|
|
}
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
check_pid_effect_axis_caps( impl, &instance );
|
|
ret = enum_object( impl, &filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
value_ofs += sizeof(LONG);
|
|
object++;
|
|
}
|
|
}
|
|
|
|
effect_caps = impl->pid_effect_update.trigger_button_caps;
|
|
|
|
for (caps = HID_INPUT_VALUE_CAPS( preparsed ), caps_end = caps + preparsed->input_caps_count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
if (!(caps->flags & HID_VALUE_CAPS_IS_BUTTON)) continue;
|
|
|
|
if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
{
|
|
TRACE( "Ignoring input button %s, vendor specific.\n", debugstr_hid_value_caps( caps ) );
|
|
button_ofs += caps->usage_max - caps->usage_min + 1;
|
|
}
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
instance.dwOfs = button_ofs;
|
|
instance.dwType = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE( button++ );
|
|
instance.dwFlags = 0;
|
|
if (effect_caps && effect_caps->logical_min <= j && effect_caps->logical_max >= j)
|
|
{
|
|
instance.dwType |= DIDFT_FFEFFECTTRIGGER;
|
|
instance.dwFlags |= DIDOI_FFEFFECTTRIGGER;
|
|
}
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
ret = enum_object( impl, &filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
button_ofs++;
|
|
object++;
|
|
}
|
|
}
|
|
|
|
for (caps = HID_OUTPUT_VALUE_CAPS( preparsed ), caps_end = caps + preparsed->output_caps_count;
|
|
caps != caps_end; ++caps)
|
|
{
|
|
if (!caps->usage_page) continue;
|
|
|
|
if (caps->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
{
|
|
TRACE( "Ignoring output caps %s, vendor specific.\n", debugstr_hid_value_caps( caps ) );
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) button_ofs += caps->usage_max - caps->usage_min + 1;
|
|
else value_ofs += (caps->usage_max - caps->usage_min + 1) * sizeof(LONG);
|
|
}
|
|
else if (caps->flags & HID_VALUE_CAPS_ARRAY_HAS_MORE)
|
|
{
|
|
for (nary_end = caps - 1; caps != caps_end; caps++)
|
|
if (!(caps->flags & HID_VALUE_CAPS_ARRAY_HAS_MORE)) break;
|
|
|
|
for (nary = caps; nary != nary_end; nary--)
|
|
{
|
|
instance.dwOfs = button_ofs;
|
|
instance.dwType = DIDFT_NODATA | DIDFT_MAKEINSTANCE( object++ ) | DIDFT_OUTPUT;
|
|
instance.dwFlags = 0x80008000;
|
|
instance.wUsagePage = nary->usage_page;
|
|
instance.wUsage = nary->usage_min;
|
|
instance.guidType = GUID_Unknown;
|
|
instance.wReportId = nary->report_id;
|
|
instance.wCollectionNumber = nary->link_collection;
|
|
ret = enum_object( impl, &filter, flags, callback, nary, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
button_ofs++;
|
|
}
|
|
}
|
|
else for (j = caps->usage_min; j <= caps->usage_max; ++j)
|
|
{
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) instance.dwOfs = button_ofs;
|
|
else instance.dwOfs = value_ofs;
|
|
|
|
instance.dwType = DIDFT_NODATA | DIDFT_MAKEINSTANCE( object++ ) | DIDFT_OUTPUT;
|
|
instance.dwFlags = 0x80008000;
|
|
instance.wUsagePage = caps->usage_page;
|
|
instance.wUsage = j;
|
|
instance.guidType = GUID_Unknown;
|
|
instance.wReportId = caps->report_id;
|
|
instance.wCollectionNumber = caps->link_collection;
|
|
ret = enum_object( impl, &filter, flags, callback, caps, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
|
|
if (caps->flags & HID_VALUE_CAPS_IS_BUTTON) button_ofs++;
|
|
else value_ofs += sizeof(LONG);
|
|
}
|
|
}
|
|
|
|
for (node = HID_COLLECTION_NODES( preparsed ), node_end = node + preparsed->number_link_collection_nodes;
|
|
node != node_end; ++node)
|
|
{
|
|
if (!node->usage_page) continue;
|
|
if (node->usage_page >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
|
|
TRACE( "Ignoring collection %s, vendor specific.\n", debugstr_hid_collection_node( node ) );
|
|
else
|
|
{
|
|
instance.dwOfs = 0;
|
|
instance.dwType = DIDFT_COLLECTION | DIDFT_MAKEINSTANCE( collection++ ) | DIDFT_NODATA;
|
|
instance.dwFlags = 0;
|
|
instance.wUsagePage = node->usage_page;
|
|
instance.wUsage = node->usage;
|
|
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
|
|
instance.wReportId = 0;
|
|
instance.wCollectionNumber = node->parent;
|
|
ret = enum_object( impl, &filter, flags, callback, NULL, &instance, data );
|
|
if (ret != DIENUM_CONTINUE) return ret;
|
|
}
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static ULONG hid_joystick_private_incref( struct hid_joystick *impl )
|
|
{
|
|
return IDirectInputDevice2WImpl_AddRef( &impl->base.IDirectInputDevice8W_iface );
|
|
}
|
|
|
|
static ULONG hid_joystick_private_decref( struct hid_joystick *impl )
|
|
{
|
|
struct hid_joystick tmp = *impl;
|
|
ULONG ref;
|
|
|
|
if (!(ref = IDirectInputDevice2WImpl_Release( &impl->base.IDirectInputDevice8W_iface )))
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, tmp.usages_buf );
|
|
HeapFree( GetProcessHeap(), 0, tmp.output_report_buf );
|
|
HeapFree( GetProcessHeap(), 0, tmp.input_report_buf );
|
|
HeapFree( GetProcessHeap(), 0, tmp.input_extra_caps );
|
|
HidD_FreePreparsedData( tmp.preparsed );
|
|
CancelIoEx( tmp.device, &tmp.read_ovl );
|
|
CloseHandle( tmp.base.read_event );
|
|
CloseHandle( tmp.device );
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_AddRef( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_Release( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
if (!ref) hid_joystick_private_decref( impl );
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetCapabilities( IDirectInputDevice8W *iface, DIDEVCAPS *caps )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
|
|
TRACE( "iface %p, caps %p.\n", iface, caps );
|
|
|
|
if (!caps) return E_POINTER;
|
|
|
|
*caps = impl->dev_caps;
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
struct enum_objects_params
|
|
{
|
|
LPDIENUMDEVICEOBJECTSCALLBACKW callback;
|
|
void *context;
|
|
};
|
|
|
|
static BOOL enum_objects_callback( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct enum_objects_params *params = data;
|
|
return params->callback( instance, params->context );
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_EnumObjects( IDirectInputDevice8W *iface, LPDIENUMDEVICEOBJECTSCALLBACKW callback,
|
|
void *context, DWORD flags )
|
|
{
|
|
static const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = DIPH_DEVICE,
|
|
};
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct enum_objects_params params =
|
|
{
|
|
.callback = callback,
|
|
.context = context,
|
|
};
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p, callback %p, context %p, flags %#x.\n", iface, callback, context, flags );
|
|
|
|
if (!callback) return DIERR_INVALIDPARAM;
|
|
if (flags & ~(DIDFT_AXIS | DIDFT_POV | DIDFT_BUTTON | DIDFT_NODATA | DIDFT_COLLECTION))
|
|
return DIERR_INVALIDPARAM;
|
|
|
|
if (flags == DIDFT_ALL || (flags & DIDFT_AXIS))
|
|
{
|
|
ret = enum_objects( impl, &filter, DIDFT_AXIS, enum_objects_callback, ¶ms );
|
|
if (ret != DIENUM_CONTINUE) return DI_OK;
|
|
}
|
|
if (flags == DIDFT_ALL || (flags & DIDFT_POV))
|
|
{
|
|
ret = enum_objects( impl, &filter, DIDFT_POV, enum_objects_callback, ¶ms );
|
|
if (ret != DIENUM_CONTINUE) return DI_OK;
|
|
}
|
|
if (flags == DIDFT_ALL || (flags & DIDFT_BUTTON))
|
|
{
|
|
ret = enum_objects( impl, &filter, DIDFT_BUTTON, enum_objects_callback, ¶ms );
|
|
if (ret != DIENUM_CONTINUE) return DI_OK;
|
|
}
|
|
if (flags == DIDFT_ALL || (flags & (DIDFT_NODATA | DIDFT_COLLECTION)))
|
|
{
|
|
ret = enum_objects( impl, &filter, DIDFT_NODATA, enum_objects_callback, ¶ms );
|
|
if (ret != DIENUM_CONTINUE) return DI_OK;
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL get_property_prop_range( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIPROPRANGE *value = data;
|
|
value->lMin = caps->physical_min;
|
|
value->lMax = caps->physical_max;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static BOOL get_property_prop_deadzone( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
struct extra_caps *extra;
|
|
DIPROPDWORD *deadzone = data;
|
|
extra = impl->input_extra_caps + (caps - HID_INPUT_VALUE_CAPS( preparsed ));
|
|
deadzone->dwData = extra->deadzone;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static BOOL get_property_prop_saturation( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
struct extra_caps *extra;
|
|
DIPROPDWORD *saturation = data;
|
|
extra = impl->input_extra_caps + (caps - HID_INPUT_VALUE_CAPS( preparsed ));
|
|
saturation->dwData = extra->saturation;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static BOOL get_property_prop_granularity( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIPROPDWORD *granularity = data;
|
|
granularity->dwData = 1;
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetProperty( IDirectInputDevice8W *iface, const GUID *guid,
|
|
DIPROPHEADER *header )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
|
|
TRACE( "iface %p, guid %s, header %p\n", iface, debugstr_guid( guid ), header );
|
|
|
|
if (!header) return DIERR_INVALIDPARAM;
|
|
if (header->dwHeaderSize != sizeof(DIPROPHEADER)) return DIERR_INVALIDPARAM;
|
|
if (!IS_DIPROP( guid )) return DI_OK;
|
|
|
|
switch (LOWORD( guid ))
|
|
{
|
|
case (DWORD_PTR)DIPROP_RANGE:
|
|
if (header->dwSize != sizeof(DIPROPRANGE)) return DIERR_INVALIDPARAM;
|
|
if (header->dwHow == DIPH_DEVICE) return DIERR_UNSUPPORTED;
|
|
if (enum_objects( impl, header, DIDFT_AXIS, get_property_prop_range, header ) == DIENUM_STOP)
|
|
return DI_OK;
|
|
return DIERR_NOTFOUND;
|
|
case (DWORD_PTR)DIPROP_DEADZONE:
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (header->dwHow == DIPH_DEVICE) return DIERR_UNSUPPORTED;
|
|
if (enum_objects( impl, header, DIDFT_AXIS, get_property_prop_deadzone, header ) == DIENUM_STOP)
|
|
return DI_OK;
|
|
return DIERR_NOTFOUND;
|
|
case (DWORD_PTR)DIPROP_SATURATION:
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (header->dwHow == DIPH_DEVICE) return DIERR_UNSUPPORTED;
|
|
if (enum_objects( impl, header, DIDFT_AXIS, get_property_prop_saturation, header ) == DIENUM_STOP)
|
|
return DI_OK;
|
|
return DIERR_NOTFOUND;
|
|
case (DWORD_PTR)DIPROP_GRANULARITY:
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (header->dwHow == DIPH_DEVICE) return DIERR_UNSUPPORTED;
|
|
if (enum_objects( impl, header, DIDFT_AXIS, get_property_prop_granularity, header ) == DIENUM_STOP)
|
|
return DI_OK;
|
|
return DIERR_NOTFOUND;
|
|
case (DWORD_PTR)DIPROP_PRODUCTNAME:
|
|
{
|
|
DIPROPSTRING *value = (DIPROPSTRING *)header;
|
|
if (header->dwSize != sizeof(DIPROPSTRING)) return DIERR_INVALIDPARAM;
|
|
lstrcpynW( value->wsz, impl->instance.tszProductName, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_INSTANCENAME:
|
|
{
|
|
DIPROPSTRING *value = (DIPROPSTRING *)header;
|
|
if (header->dwSize != sizeof(DIPROPSTRING)) return DIERR_INVALIDPARAM;
|
|
lstrcpynW( value->wsz, impl->instance.tszInstanceName, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_VIDPID:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (!impl->attrs.VendorID || !impl->attrs.ProductID) return DIERR_UNSUPPORTED;
|
|
value->dwData = MAKELONG( impl->attrs.VendorID, impl->attrs.ProductID );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_JOYSTICKID:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
value->dwData = impl->instance.guidInstance.Data3;
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_GUIDANDPATH:
|
|
{
|
|
DIPROPGUIDANDPATH *value = (DIPROPGUIDANDPATH *)header;
|
|
if (header->dwSize != sizeof(DIPROPGUIDANDPATH)) return DIERR_INVALIDPARAM;
|
|
lstrcpynW( value->wszPath, impl->device_path, MAX_PATH );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_AUTOCENTER:
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
return DIERR_UNSUPPORTED;
|
|
default:
|
|
return IDirectInputDevice2WImpl_GetProperty( iface, guid, header );
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL set_property_prop_range( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIPROPRANGE *value = data;
|
|
LONG tmp;
|
|
|
|
caps->physical_min = value->lMin;
|
|
caps->physical_max = value->lMax;
|
|
|
|
if (instance->dwType & DIDFT_AXIS)
|
|
{
|
|
if (!caps->physical_min) tmp = caps->physical_max / 2;
|
|
else tmp = round( (caps->physical_min + caps->physical_max) / 2.0 );
|
|
*(LONG *)(impl->device_state + instance->dwOfs) = tmp;
|
|
}
|
|
else if (instance->dwType & DIDFT_POV)
|
|
{
|
|
tmp = caps->logical_max - caps->logical_min;
|
|
if (tmp > 0) caps->physical_max -= value->lMax / (tmp + 1);
|
|
*(LONG *)(impl->device_state + instance->dwOfs) = -1;
|
|
}
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL set_property_prop_deadzone( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
struct extra_caps *extra;
|
|
DIPROPDWORD *deadzone = data;
|
|
extra = impl->input_extra_caps + (caps - HID_INPUT_VALUE_CAPS( preparsed ));
|
|
extra->deadzone = deadzone->dwData;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL set_property_prop_saturation( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
struct extra_caps *extra;
|
|
DIPROPDWORD *saturation = data;
|
|
extra = impl->input_extra_caps + (caps - HID_INPUT_VALUE_CAPS( preparsed ));
|
|
extra->saturation = saturation->dwData;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_SetProperty( IDirectInputDevice8W *iface, const GUID *guid,
|
|
const DIPROPHEADER *header )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, guid %s, header %p\n", iface, debugstr_guid( guid ), header );
|
|
|
|
if (!header) return DIERR_INVALIDPARAM;
|
|
if (header->dwHeaderSize != sizeof(DIPROPHEADER)) return DIERR_INVALIDPARAM;
|
|
if (!IS_DIPROP( guid )) return DI_OK;
|
|
|
|
switch (LOWORD( guid ))
|
|
{
|
|
case (DWORD_PTR)DIPROP_RANGE:
|
|
{
|
|
DIPROPRANGE *value = (DIPROPRANGE *)header;
|
|
if (header->dwSize != sizeof(DIPROPRANGE)) return DIERR_INVALIDPARAM;
|
|
if (value->lMin > value->lMax) return DIERR_INVALIDPARAM;
|
|
enum_objects( impl, header, DIDFT_AXIS, set_property_prop_range, (void *)header );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_DEADZONE:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (value->dwData > 10000) return DIERR_INVALIDPARAM;
|
|
enum_objects( impl, header, DIDFT_AXIS, set_property_prop_deadzone, (void *)header );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_SATURATION:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
if (value->dwData > 10000) return DIERR_INVALIDPARAM;
|
|
enum_objects( impl, header, DIDFT_AXIS, set_property_prop_saturation, (void *)header );
|
|
return DI_OK;
|
|
}
|
|
case (DWORD_PTR)DIPROP_AUTOCENTER:
|
|
{
|
|
DIPROPDWORD *value = (DIPROPDWORD *)header;
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
EnterCriticalSection( &impl->base.crit );
|
|
if (impl->base.acquired) hr = DIERR_ACQUIRED;
|
|
else if (value->dwData > DIPROPAUTOCENTER_ON) hr = DIERR_INVALIDPARAM;
|
|
else hr = DIERR_UNSUPPORTED;
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
return hr;
|
|
}
|
|
case (DWORD_PTR)DIPROP_FFLOAD:
|
|
case (DWORD_PTR)DIPROP_GRANULARITY:
|
|
case (DWORD_PTR)DIPROP_VIDPID:
|
|
if (header->dwSize != sizeof(DIPROPDWORD)) return DIERR_INVALIDPARAM;
|
|
return DIERR_READONLY;
|
|
case (DWORD_PTR)DIPROP_TYPENAME:
|
|
case (DWORD_PTR)DIPROP_USERNAME:
|
|
if (header->dwSize != sizeof(DIPROPSTRING)) return DIERR_INVALIDPARAM;
|
|
return DIERR_READONLY;
|
|
case (DWORD_PTR)DIPROP_GUIDANDPATH:
|
|
if (header->dwSize != sizeof(DIPROPGUIDANDPATH)) return DIERR_INVALIDPARAM;
|
|
return DIERR_READONLY;
|
|
default:
|
|
return IDirectInputDevice2WImpl_SetProperty( iface, guid, header );
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_Acquire( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG report_len = impl->caps.InputReportByteLength;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
hr = IDirectInputDevice2WImpl_Acquire( iface );
|
|
if (hr == DI_OK)
|
|
{
|
|
memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
|
|
impl->read_ovl.hEvent = impl->base.read_event;
|
|
if (ReadFile( impl->device, impl->input_report_buf, report_len, NULL, &impl->read_ovl ))
|
|
impl->base.read_callback( iface );
|
|
|
|
IDirectInputDevice8_SendForceFeedbackCommand( iface, DISFFC_RESET );
|
|
}
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_Unacquire( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
HRESULT hr;
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
if (impl->base.acquired)
|
|
{
|
|
ret = CancelIoEx( impl->device, &impl->read_ovl );
|
|
if (!ret) WARN( "CancelIoEx failed, last error %u\n", GetLastError() );
|
|
|
|
IDirectInputDevice8_SendForceFeedbackCommand( iface, DISFFC_RESET );
|
|
}
|
|
hr = IDirectInputDevice2WImpl_Unacquire( iface );
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetDeviceState( IDirectInputDevice8W *iface, DWORD len, void *ptr )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
HRESULT hr = DI_OK;
|
|
|
|
if (!ptr) return DIERR_INVALIDPARAM;
|
|
if (len != impl->base.data_format.user_df->dwDataSize) return DIERR_INVALIDPARAM;
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
if (!impl->base.acquired) hr = DIERR_NOTACQUIRED;
|
|
else fill_DataFormat( ptr, len, impl->device_state, &impl->base.data_format );
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static BOOL get_object_info( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIDEVICEOBJECTINSTANCEW *dest = data;
|
|
memcpy( dest, instance, dest->dwSize );
|
|
return DIENUM_STOP;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetObjectInfo( IDirectInputDevice8W *iface, DIDEVICEOBJECTINSTANCEW *instance,
|
|
DWORD obj, DWORD how )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = how,
|
|
.dwObj = obj
|
|
};
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p, instance %p, obj %#x, how %#x.\n", iface, instance, obj, how );
|
|
|
|
if (!instance) return E_POINTER;
|
|
if (instance->dwSize != sizeof(DIDEVICEOBJECTINSTANCE_DX3W) &&
|
|
instance->dwSize != sizeof(DIDEVICEOBJECTINSTANCEW))
|
|
return DIERR_INVALIDPARAM;
|
|
if (how == DIPH_DEVICE) return DIERR_INVALIDPARAM;
|
|
|
|
ret = enum_objects( impl, &filter, DIDFT_ALL, get_object_info, instance );
|
|
if (ret != DIENUM_CONTINUE) return DI_OK;
|
|
return DIERR_NOTFOUND;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetDeviceInfo( IDirectInputDevice8W *iface, DIDEVICEINSTANCEW *instance )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
|
|
TRACE( "iface %p, instance %p.\n", iface, instance );
|
|
|
|
if (!instance) return E_POINTER;
|
|
if (instance->dwSize != sizeof(DIDEVICEINSTANCE_DX3W) &&
|
|
instance->dwSize != sizeof(DIDEVICEINSTANCEW))
|
|
return DIERR_INVALIDPARAM;
|
|
|
|
memcpy( instance, &impl->instance, instance->dwSize );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_effect_create( struct hid_joystick *joystick, IDirectInputEffect **out );
|
|
|
|
static HRESULT WINAPI hid_joystick_CreateEffect( IDirectInputDevice8W *iface, const GUID *guid,
|
|
const DIEFFECT *params, IDirectInputEffect **out,
|
|
IUnknown *outer )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
DWORD flags = DIEP_ALLPARAMS;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, guid %s, params %p, out %p, outer %p\n", iface, debugstr_guid( guid ),
|
|
params, out, outer );
|
|
|
|
if (!out) return E_POINTER;
|
|
*out = NULL;
|
|
|
|
if (!(impl->dev_caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_UNSUPPORTED;
|
|
if (FAILED(hr = hid_joystick_effect_create( impl, out ))) return hr;
|
|
|
|
hr = IDirectInputEffect_Initialize( *out, DINPUT_instance, impl->base.dinput->dwVersion, guid );
|
|
if (FAILED(hr)) goto failed;
|
|
|
|
if (!params) return DI_OK;
|
|
if (!impl->base.acquired || !(impl->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
flags |= DIEP_NODOWNLOAD;
|
|
hr = IDirectInputEffect_SetParameters( *out, params, flags );
|
|
if (FAILED(hr)) goto failed;
|
|
return hr;
|
|
|
|
failed:
|
|
IDirectInputEffect_Release( *out );
|
|
*out = NULL;
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_EnumEffects( IDirectInputDevice8W *iface, LPDIENUMEFFECTSCALLBACKW callback,
|
|
void *context, DWORD type )
|
|
{
|
|
DIEFFECTINFOW info = {.dwSize = sizeof(info)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, callback %p, context %p, type %#x.\n", iface, callback, context, type );
|
|
|
|
if (!callback) return DIERR_INVALIDPARAM;
|
|
|
|
type = DIEFT_GETTYPE( type );
|
|
|
|
if (type == DIEFT_ALL || type == DIEFT_CONSTANTFORCE)
|
|
{
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_ConstantForce );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
}
|
|
|
|
if (type == DIEFT_ALL || type == DIEFT_RAMPFORCE)
|
|
{
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_RampForce );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
}
|
|
|
|
if (type == DIEFT_ALL || type == DIEFT_PERIODIC)
|
|
{
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Square );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Sine );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Triangle );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_SawtoothUp );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_SawtoothDown );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
}
|
|
|
|
if (type == DIEFT_ALL || type == DIEFT_CONDITION)
|
|
{
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Spring );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Damper );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Inertia );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
|
|
hr = IDirectInputDevice8_GetEffectInfo( iface, &info, &GUID_Friction );
|
|
if (FAILED(hr) && hr != DIERR_DEVICENOTREG) return hr;
|
|
if (hr == DI_OK && callback( &info, context ) == DIENUM_STOP) return DI_OK;
|
|
}
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetEffectInfo( IDirectInputDevice8W *iface, DIEFFECTINFOW *info,
|
|
const GUID *guid )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->preparsed;
|
|
HIDP_BUTTON_CAPS button;
|
|
ULONG type, collection;
|
|
NTSTATUS status;
|
|
USAGE usage = 0;
|
|
USHORT count;
|
|
|
|
TRACE( "iface %p, info %p, guid %s.\n", iface, info, debugstr_guid( guid ) );
|
|
|
|
if (!info) return E_POINTER;
|
|
if (info->dwSize != sizeof(DIEFFECTINFOW)) return DIERR_INVALIDPARAM;
|
|
if (!(impl->dev_caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_DEVICENOTREG;
|
|
|
|
switch ((usage = effect_guid_to_usage( guid )))
|
|
{
|
|
case PID_USAGE_ET_SQUARE:
|
|
case PID_USAGE_ET_SINE:
|
|
case PID_USAGE_ET_TRIANGLE:
|
|
case PID_USAGE_ET_SAWTOOTH_UP:
|
|
case PID_USAGE_ET_SAWTOOTH_DOWN:
|
|
type = DIEFT_PERIODIC;
|
|
break;
|
|
case PID_USAGE_ET_SPRING:
|
|
case PID_USAGE_ET_DAMPER:
|
|
case PID_USAGE_ET_INERTIA:
|
|
case PID_USAGE_ET_FRICTION:
|
|
type = DIEFT_CONDITION;
|
|
break;
|
|
case PID_USAGE_ET_CONSTANT_FORCE:
|
|
type = DIEFT_CONSTANTFORCE;
|
|
break;
|
|
case PID_USAGE_ET_RAMP:
|
|
type = DIEFT_RAMPFORCE;
|
|
break;
|
|
case PID_USAGE_ET_CUSTOM_FORCE_DATA:
|
|
type = DIEFT_CUSTOMFORCE;
|
|
break;
|
|
default:
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
|
|
if (!(collection = effect_update->collection)) return DIERR_DEVICENOTREG;
|
|
|
|
info->dwDynamicParams = DIEP_TYPESPECIFICPARAMS;
|
|
if (effect_update->axis_count) info->dwDynamicParams |= DIEP_AXES;
|
|
if (effect_update->duration_caps) info->dwDynamicParams |= DIEP_DURATION;
|
|
if (effect_update->gain_caps) info->dwDynamicParams |= DIEP_GAIN;
|
|
if (effect_update->sample_period_caps) info->dwDynamicParams |= DIEP_SAMPLEPERIOD;
|
|
if (effect_update->start_delay_caps)
|
|
{
|
|
type |= DIEFT_STARTDELAY;
|
|
info->dwDynamicParams |= DIEP_STARTDELAY;
|
|
}
|
|
if (effect_update->trigger_button_caps) info->dwDynamicParams |= DIEP_TRIGGERBUTTON;
|
|
if (effect_update->trigger_repeat_interval_caps) info->dwDynamicParams |= DIEP_TRIGGERREPEATINTERVAL;
|
|
if (effect_update->axes_coll) info->dwDynamicParams |= DIEP_AXES;
|
|
|
|
if (!(collection = effect_update->type_coll)) return DIERR_DEVICENOTREG;
|
|
else
|
|
{
|
|
count = 1;
|
|
status = HidP_GetSpecificButtonCaps( HidP_Output, HID_USAGE_PAGE_PID, collection,
|
|
usage, &button, &count, preparsed );
|
|
if (status != HIDP_STATUS_SUCCESS)
|
|
{
|
|
WARN( "HidP_GetSpecificValueCaps %#x returned %#x\n", usage, status );
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
else if (!count)
|
|
{
|
|
WARN( "effect usage %#x not found\n", usage );
|
|
return DIERR_DEVICENOTREG;
|
|
}
|
|
}
|
|
|
|
info->guid = *guid;
|
|
info->dwEffType = type;
|
|
info->dwStaticParams = info->dwDynamicParams;
|
|
lstrcpynW( info->tszName, effect_guid_to_string( guid ), MAX_PATH );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_GetForceFeedbackState( IDirectInputDevice8W *iface, DWORD *out )
|
|
{
|
|
FIXME( "iface %p, out %p stub!\n", iface, out );
|
|
|
|
if (!out) return E_POINTER;
|
|
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_SendForceFeedbackCommand( IDirectInputDevice8W *iface, DWORD command )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct pid_control_report *report = &impl->pid_device_control;
|
|
ULONG report_len = impl->caps.OutputReportByteLength;
|
|
char *report_buf = impl->output_report_buf;
|
|
NTSTATUS status;
|
|
USAGE usage;
|
|
ULONG count;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, flags %x.\n", iface, command );
|
|
|
|
switch (command)
|
|
{
|
|
case DISFFC_RESET: usage = PID_USAGE_DC_DEVICE_RESET; break;
|
|
case DISFFC_STOPALL: usage = PID_USAGE_DC_STOP_ALL_EFFECTS; break;
|
|
case DISFFC_PAUSE: usage = PID_USAGE_DC_DEVICE_PAUSE; break;
|
|
case DISFFC_CONTINUE: usage = PID_USAGE_DC_DEVICE_CONTINUE; break;
|
|
case DISFFC_SETACTUATORSON: usage = PID_USAGE_DC_ENABLE_ACTUATORS; break;
|
|
case DISFFC_SETACTUATORSOFF: usage = PID_USAGE_DC_DISABLE_ACTUATORS; break;
|
|
default: return DIERR_INVALIDPARAM;
|
|
}
|
|
|
|
if (!(impl->dev_caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_UNSUPPORTED;
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
if (!impl->base.acquired || !(impl->base.dwCoopLevel & DISCL_EXCLUSIVE))
|
|
hr = DIERR_NOTEXCLUSIVEACQUIRED;
|
|
else
|
|
{
|
|
count = 1;
|
|
status = HidP_InitializeReportForID( HidP_Output, report->id, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else status = HidP_SetUsages( HidP_Output, HID_USAGE_PAGE_PID, report->control_coll, &usage,
|
|
&count, impl->preparsed, report_buf, report_len );
|
|
|
|
if (status != HIDP_STATUS_SUCCESS) hr = status;
|
|
else if (WriteFile( impl->device, report_buf, report_len, NULL, NULL )) hr = DI_OK;
|
|
else hr = DIERR_GENERIC;
|
|
}
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_EnumCreatedEffectObjects( IDirectInputDevice8W *iface,
|
|
LPDIENUMCREATEDEFFECTOBJECTSCALLBACK callback,
|
|
void *context, DWORD flags )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
struct hid_joystick_effect *effect, *next;
|
|
|
|
FIXME( "iface %p, callback %p, context %p, flags %#x stub!\n", iface, callback, context, flags );
|
|
|
|
if (!callback) return DIERR_INVALIDPARAM;
|
|
if (flags) return DIERR_INVALIDPARAM;
|
|
|
|
LIST_FOR_EACH_ENTRY_SAFE(effect, next, &impl->effect_list, struct hid_joystick_effect, entry)
|
|
if (callback( &effect->IDirectInputEffect_iface, context ) != DIENUM_CONTINUE) break;
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_Poll( IDirectInputDevice8W *iface )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
HRESULT hr = DI_NOEFFECT;
|
|
|
|
EnterCriticalSection( &impl->base.crit );
|
|
if (!impl->base.acquired) hr = DIERR_NOTACQUIRED;
|
|
LeaveCriticalSection( &impl->base.crit );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_BuildActionMap( IDirectInputDevice8W *iface, DIACTIONFORMATW *format,
|
|
const WCHAR *username, DWORD flags )
|
|
{
|
|
FIXME( "iface %p, format %p, username %s, flags %#x stub!\n", iface, format, debugstr_w(username), flags );
|
|
|
|
if (!format) return DIERR_INVALIDPARAM;
|
|
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_SetActionMap( IDirectInputDevice8W *iface, DIACTIONFORMATW *format,
|
|
const WCHAR *username, DWORD flags )
|
|
{
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
|
|
TRACE( "iface %p, format %p, username %s, flags %#x.\n", iface, format, debugstr_w(username), flags );
|
|
|
|
if (!format) return DIERR_INVALIDPARAM;
|
|
|
|
return _set_action_map( iface, format, username, flags, impl->base.data_format.wine_df );
|
|
}
|
|
|
|
static const IDirectInputDevice8WVtbl hid_joystick_vtbl =
|
|
{
|
|
/*** IUnknown methods ***/
|
|
IDirectInputDevice2WImpl_QueryInterface,
|
|
hid_joystick_AddRef,
|
|
hid_joystick_Release,
|
|
/*** IDirectInputDevice methods ***/
|
|
hid_joystick_GetCapabilities,
|
|
hid_joystick_EnumObjects,
|
|
hid_joystick_GetProperty,
|
|
hid_joystick_SetProperty,
|
|
hid_joystick_Acquire,
|
|
hid_joystick_Unacquire,
|
|
hid_joystick_GetDeviceState,
|
|
IDirectInputDevice2WImpl_GetDeviceData,
|
|
IDirectInputDevice2WImpl_SetDataFormat,
|
|
IDirectInputDevice2WImpl_SetEventNotification,
|
|
IDirectInputDevice2WImpl_SetCooperativeLevel,
|
|
hid_joystick_GetObjectInfo,
|
|
hid_joystick_GetDeviceInfo,
|
|
IDirectInputDevice2WImpl_RunControlPanel,
|
|
IDirectInputDevice2WImpl_Initialize,
|
|
/*** IDirectInputDevice2 methods ***/
|
|
hid_joystick_CreateEffect,
|
|
hid_joystick_EnumEffects,
|
|
hid_joystick_GetEffectInfo,
|
|
hid_joystick_GetForceFeedbackState,
|
|
hid_joystick_SendForceFeedbackCommand,
|
|
hid_joystick_EnumCreatedEffectObjects,
|
|
IDirectInputDevice2WImpl_Escape,
|
|
hid_joystick_Poll,
|
|
IDirectInputDevice2WImpl_SendDeviceData,
|
|
/*** IDirectInputDevice7 methods ***/
|
|
IDirectInputDevice7WImpl_EnumEffectsInFile,
|
|
IDirectInputDevice7WImpl_WriteEffectToFile,
|
|
/*** IDirectInputDevice8 methods ***/
|
|
hid_joystick_BuildActionMap,
|
|
hid_joystick_SetActionMap,
|
|
IDirectInputDevice8WImpl_GetImageInfo,
|
|
};
|
|
|
|
struct parse_device_state_params
|
|
{
|
|
BYTE old_state[DEVICE_STATE_MAX_SIZE];
|
|
BYTE buttons[128];
|
|
DWORD time;
|
|
DWORD seq;
|
|
};
|
|
|
|
static BOOL check_device_state_button( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
|
|
struct parse_device_state_params *params = data;
|
|
BYTE old_value, value;
|
|
|
|
if (instance->wReportId != impl->device_state_report_id) return DIENUM_CONTINUE;
|
|
|
|
value = params->buttons[instance->wUsage - 1];
|
|
old_value = params->old_state[instance->dwOfs];
|
|
impl->device_state[instance->dwOfs] = value;
|
|
if (old_value != value)
|
|
queue_event( iface, instance->dwType, value, params->time, params->seq );
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static LONG sign_extend( ULONG value, struct hid_value_caps *caps )
|
|
{
|
|
UINT sign = 1 << (caps->bit_size - 1);
|
|
if (sign <= 1 || caps->logical_min >= 0) return value;
|
|
return value - ((value & sign) << 1);
|
|
}
|
|
|
|
static LONG scale_value( ULONG value, struct hid_value_caps *caps, LONG min, LONG max )
|
|
{
|
|
LONG tmp = sign_extend( value, caps );
|
|
if (caps->logical_min > tmp || caps->logical_max < tmp) return -1; /* invalid / null value */
|
|
return min + MulDiv( tmp - caps->logical_min, max - min, caps->logical_max - caps->logical_min );
|
|
}
|
|
|
|
static LONG scale_axis_value( ULONG value, struct hid_value_caps *caps, struct extra_caps *extra )
|
|
{
|
|
LONG tmp = sign_extend( value, caps ), log_ctr, log_min, log_max, phy_ctr, phy_min, phy_max;
|
|
ULONG bit_max = (1 << caps->bit_size) - 1;
|
|
|
|
log_min = caps->logical_min;
|
|
log_max = caps->logical_max;
|
|
phy_min = caps->physical_min;
|
|
phy_max = caps->physical_max;
|
|
/* xinput HID gamepad have bogus logical value range, let's use the bit range instead */
|
|
if (log_min == 0 && log_max == -1) log_max = bit_max;
|
|
|
|
if (phy_min == 0) phy_ctr = phy_max >> 1;
|
|
else phy_ctr = round( (phy_min + phy_max) / 2.0 );
|
|
if (log_min == 0) log_ctr = log_max >> 1;
|
|
else log_ctr = round( (log_min + log_max) / 2.0 );
|
|
|
|
tmp -= log_ctr;
|
|
if (tmp <= 0)
|
|
{
|
|
log_max = MulDiv( log_min - log_ctr, extra->deadzone, 10000 );
|
|
log_min = MulDiv( log_min - log_ctr, extra->saturation, 10000 );
|
|
phy_max = phy_ctr;
|
|
}
|
|
else
|
|
{
|
|
log_min = MulDiv( log_max - log_ctr, extra->deadzone, 10000 );
|
|
log_max = MulDiv( log_max - log_ctr, extra->saturation, 10000 );
|
|
phy_min = phy_ctr;
|
|
}
|
|
|
|
if (tmp <= log_min) return phy_min;
|
|
if (tmp >= log_max) return phy_max;
|
|
return phy_min + MulDiv( tmp - log_min, phy_max - phy_min, log_max - log_min );
|
|
}
|
|
|
|
static BOOL read_device_state_value( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct hid_preparsed_data *preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
IDirectInputDevice8W *iface = &impl->base.IDirectInputDevice8W_iface;
|
|
ULONG logical_value, report_len = impl->caps.InputReportByteLength;
|
|
struct parse_device_state_params *params = data;
|
|
char *report_buf = impl->input_report_buf;
|
|
struct extra_caps *extra;
|
|
LONG old_value, value;
|
|
NTSTATUS status;
|
|
|
|
if (instance->wReportId != impl->device_state_report_id) return DIENUM_CONTINUE;
|
|
|
|
extra = impl->input_extra_caps + (caps - HID_INPUT_VALUE_CAPS( preparsed ));
|
|
status = HidP_GetUsageValue( HidP_Input, instance->wUsagePage, 0, instance->wUsage,
|
|
&logical_value, impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsageValue %04x:%04x returned %#x\n",
|
|
instance->wUsagePage, instance->wUsage, status );
|
|
if (instance->dwType & DIDFT_AXIS) value = scale_axis_value( logical_value, caps, extra );
|
|
else value = scale_value( logical_value, caps, caps->physical_min, caps->physical_max );
|
|
|
|
old_value = *(LONG *)(params->old_state + instance->dwOfs);
|
|
*(LONG *)(impl->device_state + instance->dwOfs) = value;
|
|
if (old_value != value)
|
|
queue_event( iface, instance->dwType, value, params->time, params->seq );
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_read_state( IDirectInputDevice8W *iface )
|
|
{
|
|
static const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = DIPH_DEVICE,
|
|
};
|
|
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
|
|
ULONG i, count, report_len = impl->caps.InputReportByteLength;
|
|
DIDATAFORMAT *format = impl->base.data_format.wine_df;
|
|
struct parse_device_state_params params = {{0}};
|
|
char *report_buf = impl->input_report_buf;
|
|
USAGE_AND_PAGE *usages;
|
|
NTSTATUS status;
|
|
BOOL ret;
|
|
|
|
ret = GetOverlappedResult( impl->device, &impl->read_ovl, &count, FALSE );
|
|
if (!ret) WARN( "ReadFile failed, error %u\n", GetLastError() );
|
|
else if (TRACE_ON(dinput))
|
|
{
|
|
TRACE( "read size %u report:\n", count );
|
|
for (i = 0; i < report_len;)
|
|
{
|
|
char buffer[256], *buf = buffer;
|
|
buf += sprintf(buf, "%08x ", i);
|
|
do
|
|
{
|
|
buf += sprintf(buf, " %02x", (BYTE)report_buf[i] );
|
|
} while (++i % 16 && i < report_len);
|
|
TRACE("%s\n", buffer);
|
|
}
|
|
}
|
|
|
|
do
|
|
{
|
|
count = impl->usages_count;
|
|
memset( impl->usages_buf, 0, count * sizeof(*impl->usages_buf) );
|
|
status = HidP_GetUsagesEx( HidP_Input, 0, impl->usages_buf, &count,
|
|
impl->preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsagesEx returned %#x\n", status );
|
|
|
|
if (report_buf[0] == impl->device_state_report_id)
|
|
{
|
|
params.time = GetCurrentTime();
|
|
params.seq = impl->base.dinput->evsequence++;
|
|
memcpy( params.old_state, impl->device_state, format->dwDataSize );
|
|
memset( impl->device_state, 0, format->dwDataSize );
|
|
|
|
while (count--)
|
|
{
|
|
usages = impl->usages_buf + count;
|
|
if (usages->UsagePage != HID_USAGE_PAGE_BUTTON)
|
|
FIXME( "unimplemented usage page %x.\n", usages->UsagePage );
|
|
else if (usages->Usage >= 128)
|
|
FIXME( "ignoring extraneous button %d.\n", usages->Usage );
|
|
else
|
|
params.buttons[usages->Usage - 1] = 0x80;
|
|
}
|
|
|
|
enum_objects( impl, &filter, DIDFT_AXIS | DIDFT_POV, read_device_state_value, ¶ms );
|
|
enum_objects( impl, &filter, DIDFT_BUTTON, check_device_state_button, ¶ms );
|
|
if (impl->base.hEvent && memcmp( ¶ms.old_state, impl->device_state, format->dwDataSize ))
|
|
SetEvent( impl->base.hEvent );
|
|
}
|
|
|
|
memset( &impl->read_ovl, 0, sizeof(impl->read_ovl) );
|
|
impl->read_ovl.hEvent = impl->base.read_event;
|
|
} while (ReadFile( impl->device, report_buf, report_len, &count, &impl->read_ovl ));
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static DWORD device_type_for_version( DWORD type, DWORD version )
|
|
{
|
|
if (version >= 0x0800) return type;
|
|
|
|
switch (GET_DIDEVICE_TYPE( type ))
|
|
{
|
|
case DI8DEVTYPE_JOYSTICK:
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPEJOYSTICK_LIMITED)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_TRADITIONAL << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_GAMEPAD:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_GAMEPAD << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_DRIVING:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_WHEEL << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_FLIGHT:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_FLIGHTSTICK << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_SUPPLEMENTAL:
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPESUPPLEMENTAL_HEADTRACKER)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_HEADTRACKER << 8) | DIDEVTYPE_HID;
|
|
if (GET_DIDEVICE_SUBTYPE( type ) == DI8DEVTYPESUPPLEMENTAL_RUDDERPEDALS)
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_RUDDER << 8) | DIDEVTYPE_HID;
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
case DI8DEVTYPE_1STPERSON:
|
|
return DIDEVTYPE_JOYSTICK | (DIDEVTYPEJOYSTICK_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
default:
|
|
return DIDEVTYPE_DEVICE | DIDEVTYPE_HID;
|
|
}
|
|
}
|
|
|
|
static BOOL hid_joystick_device_try_open( UINT32 handle, const WCHAR *path, HANDLE *device,
|
|
PHIDP_PREPARSED_DATA *preparsed, HIDD_ATTRIBUTES *attrs,
|
|
HIDP_CAPS *caps, DIDEVICEINSTANCEW *instance, DWORD version )
|
|
{
|
|
PHIDP_PREPARSED_DATA preparsed_data = NULL;
|
|
DWORD type = 0, button_count = 0;
|
|
HIDP_BUTTON_CAPS buttons[10];
|
|
HIDP_VALUE_CAPS value;
|
|
HANDLE device_file;
|
|
NTSTATUS status;
|
|
USHORT count;
|
|
|
|
device_file = CreateFileW( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 );
|
|
if (device_file == INVALID_HANDLE_VALUE) return FALSE;
|
|
|
|
if (!HidD_GetPreparsedData( device_file, &preparsed_data )) goto failed;
|
|
if (!HidD_GetAttributes( device_file, attrs )) goto failed;
|
|
if (HidP_GetCaps( preparsed_data, caps ) != HIDP_STATUS_SUCCESS) goto failed;
|
|
|
|
if (caps->UsagePage == HID_USAGE_PAGE_GAME) FIXME( "game usage page not implemented!\n" );
|
|
if (caps->UsagePage == HID_USAGE_PAGE_SIMULATION) FIXME( "simulation usage page not implemented!\n" );
|
|
if (caps->UsagePage != HID_USAGE_PAGE_GENERIC) goto failed;
|
|
if (caps->Usage != HID_USAGE_GENERIC_GAMEPAD && caps->Usage != HID_USAGE_GENERIC_JOYSTICK) goto failed;
|
|
|
|
if (!HidD_GetProductString( device_file, instance->tszInstanceName, MAX_PATH )) goto failed;
|
|
if (!HidD_GetProductString( device_file, instance->tszProductName, MAX_PATH )) goto failed;
|
|
|
|
instance->guidInstance = hid_joystick_guid;
|
|
instance->guidInstance.Data1 ^= handle;
|
|
instance->guidProduct = DInput_PIDVID_Product_GUID;
|
|
instance->guidProduct.Data1 = MAKELONG( attrs->VendorID, attrs->ProductID );
|
|
instance->guidFFDriver = GUID_NULL;
|
|
instance->wUsagePage = caps->UsagePage;
|
|
instance->wUsage = caps->Usage;
|
|
|
|
count = ARRAY_SIZE(buttons);
|
|
status = HidP_GetSpecificButtonCaps( HidP_Output, HID_USAGE_PAGE_PID, 0,
|
|
PID_USAGE_DC_DEVICE_RESET, buttons, &count, preparsed_data );
|
|
if (status == HIDP_STATUS_SUCCESS && count > 0)
|
|
instance->guidFFDriver = IID_IDirectInputPIDDriver;
|
|
|
|
count = ARRAY_SIZE(buttons);
|
|
status = HidP_GetSpecificButtonCaps( HidP_Input, HID_USAGE_PAGE_BUTTON, 0, 0, buttons, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS) count = button_count = 0;
|
|
while (count--)
|
|
{
|
|
if (!buttons[count].IsRange) button_count += 1;
|
|
else button_count += buttons[count].Range.UsageMax - buttons[count].Range.UsageMin + 1;
|
|
}
|
|
|
|
switch (caps->Usage)
|
|
{
|
|
case HID_USAGE_GENERIC_GAMEPAD:
|
|
type = DI8DEVTYPE_GAMEPAD | DIDEVTYPE_HID;
|
|
if (button_count < 6) type |= DI8DEVTYPEGAMEPAD_LIMITED << 8;
|
|
else type |= DI8DEVTYPEGAMEPAD_STANDARD << 8;
|
|
break;
|
|
case HID_USAGE_GENERIC_JOYSTICK:
|
|
type = DI8DEVTYPE_JOYSTICK | DIDEVTYPE_HID;
|
|
if (button_count < 5) type |= DI8DEVTYPEJOYSTICK_LIMITED << 8;
|
|
else type |= DI8DEVTYPEJOYSTICK_STANDARD << 8;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
|
|
HID_USAGE_GENERIC_Z, &value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DIDEVTYPE_HID;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0,
|
|
HID_USAGE_GENERIC_HATSWITCH, &value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_JOYSTICK | (DI8DEVTYPEJOYSTICK_LIMITED << 8) | DIDEVTYPE_HID;
|
|
|
|
break;
|
|
}
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_X,
|
|
&value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Input, HID_USAGE_PAGE_GENERIC, 0, HID_USAGE_GENERIC_Y,
|
|
&value, &count, preparsed_data );
|
|
if (status != HIDP_STATUS_SUCCESS || !count)
|
|
type = DI8DEVTYPE_SUPPLEMENTAL | (DI8DEVTYPESUPPLEMENTAL_UNKNOWN << 8) | DIDEVTYPE_HID;
|
|
|
|
instance->dwDevType = device_type_for_version( type, version );
|
|
|
|
*device = device_file;
|
|
*preparsed = preparsed_data;
|
|
return TRUE;
|
|
|
|
failed:
|
|
CloseHandle( device_file );
|
|
HidD_FreePreparsedData( preparsed_data );
|
|
return FALSE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_device_open( int index, DIDEVICEINSTANCEW *filter, WCHAR *device_path,
|
|
HANDLE *device, PHIDP_PREPARSED_DATA *preparsed,
|
|
HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps, DWORD version )
|
|
{
|
|
static const WCHAR ig_w[] = {'&','I','G','_',0};
|
|
static const WCHAR xi_w[] = {'&','X','I','_',0};
|
|
char buffer[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + MAX_PATH * sizeof(WCHAR)];
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer;
|
|
SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof(iface)};
|
|
SP_DEVINFO_DATA devinfo = {.cbSize = sizeof(devinfo)};
|
|
DIDEVICEINSTANCEW instance = *filter;
|
|
WCHAR device_id[MAX_PATH], *tmp;
|
|
HDEVINFO set, xi_set;
|
|
UINT32 i = 0, handle;
|
|
BOOL override;
|
|
DWORD type;
|
|
GUID hid;
|
|
|
|
TRACE( "index %d, product %s, instance %s\n", index, debugstr_guid( &filter->guidProduct ),
|
|
debugstr_guid( &filter->guidInstance ) );
|
|
|
|
HidD_GetHidGuid( &hid );
|
|
|
|
set = SetupDiGetClassDevsW( &hid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT );
|
|
if (set == INVALID_HANDLE_VALUE) return DIERR_DEVICENOTREG;
|
|
xi_set = SetupDiGetClassDevsW( &GUID_DEVINTERFACE_WINEXINPUT, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT );
|
|
|
|
*device = NULL;
|
|
*preparsed = NULL;
|
|
while (SetupDiEnumDeviceInterfaces( set, NULL, &hid, i++, &iface ))
|
|
{
|
|
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
|
if (!SetupDiGetDeviceInterfaceDetailW( set, &iface, detail, sizeof(buffer), NULL, &devinfo ))
|
|
continue;
|
|
if (!SetupDiGetDevicePropertyW( set, &devinfo, &DEVPROPKEY_HID_HANDLE, &type,
|
|
(BYTE *)&handle, sizeof(handle), NULL, 0 ) ||
|
|
type != DEVPROP_TYPE_UINT32)
|
|
continue;
|
|
if (!hid_joystick_device_try_open( handle, detail->DevicePath, device, preparsed,
|
|
attrs, caps, &instance, version ))
|
|
continue;
|
|
|
|
if (device_instance_is_disabled( &instance, &override ))
|
|
goto next;
|
|
|
|
if (override)
|
|
{
|
|
if (!SetupDiGetDeviceInstanceIdW( set, &devinfo, device_id, MAX_PATH, NULL ) ||
|
|
!(tmp = strstrW( device_id, ig_w )))
|
|
goto next;
|
|
memcpy( tmp, xi_w, sizeof(xi_w) - sizeof(WCHAR) );
|
|
if (!SetupDiOpenDeviceInfoW( xi_set, device_id, NULL, 0, &devinfo ))
|
|
goto next;
|
|
if (!SetupDiEnumDeviceInterfaces( xi_set, &devinfo, &GUID_DEVINTERFACE_WINEXINPUT, 0, &iface ))
|
|
goto next;
|
|
if (!SetupDiGetDeviceInterfaceDetailW( xi_set, &iface, detail, sizeof(buffer), NULL, &devinfo ))
|
|
goto next;
|
|
|
|
CloseHandle( *device );
|
|
HidD_FreePreparsedData( *preparsed );
|
|
if (!hid_joystick_device_try_open( handle, detail->DevicePath, device, preparsed,
|
|
attrs, caps, &instance, version ))
|
|
continue;
|
|
}
|
|
|
|
/* enumerate device by GUID */
|
|
if (index < 0 && IsEqualGUID( &filter->guidProduct, &instance.guidProduct )) break;
|
|
if (index < 0 && IsEqualGUID( &filter->guidInstance, &instance.guidInstance )) break;
|
|
|
|
/* enumerate all devices */
|
|
if (index >= 0 && !index--) break;
|
|
|
|
next:
|
|
CloseHandle( *device );
|
|
HidD_FreePreparsedData( *preparsed );
|
|
*device = NULL;
|
|
*preparsed = NULL;
|
|
}
|
|
|
|
if (xi_set != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList( xi_set );
|
|
SetupDiDestroyDeviceInfoList( set );
|
|
if (!*device || !*preparsed) return DIERR_DEVICENOTREG;
|
|
|
|
lstrcpynW( device_path, detail->DevicePath, MAX_PATH );
|
|
*filter = instance;
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT hid_joystick_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW *instance,
|
|
DWORD version, int index )
|
|
{
|
|
HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)};
|
|
PHIDP_PREPARSED_DATA preparsed;
|
|
WCHAR device_path[MAX_PATH];
|
|
HIDP_CAPS caps;
|
|
HANDLE device;
|
|
HRESULT hr;
|
|
|
|
TRACE( "type %#x, flags %#x, instance %p, version %#04x, index %d\n", type, flags, instance, version, index );
|
|
|
|
hr = hid_joystick_device_open( index, instance, device_path, &device, &preparsed,
|
|
&attrs, &caps, version );
|
|
if (hr != DI_OK) return hr;
|
|
|
|
HidD_FreePreparsedData( preparsed );
|
|
CloseHandle( device );
|
|
|
|
if (instance->dwSize != sizeof(DIDEVICEINSTANCEW))
|
|
return S_FALSE;
|
|
if (version < 0x0800 && type != DIDEVTYPE_JOYSTICK)
|
|
return S_FALSE;
|
|
if (version >= 0x0800 && type != DI8DEVCLASS_ALL && type != DI8DEVCLASS_GAMECTRL)
|
|
return S_FALSE;
|
|
|
|
TRACE( "found device %s, usage %04x:%04x, product %s, instance %s, name %s\n", debugstr_w(device_path),
|
|
instance->wUsagePage, instance->wUsage, debugstr_guid( &instance->guidProduct ),
|
|
debugstr_guid( &instance->guidInstance ), debugstr_w(instance->tszInstanceName) );
|
|
|
|
return DI_OK;
|
|
}
|
|
|
|
static BOOL init_objects( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIDATAFORMAT *format = impl->base.data_format.wine_df;
|
|
|
|
format->dwNumObjs++;
|
|
format->dwDataSize = max( format->dwDataSize, instance->dwOfs + sizeof(LONG) );
|
|
if (instance->dwType & DIDFT_BUTTON) impl->dev_caps.dwButtons++;
|
|
if (instance->dwType & DIDFT_AXIS) impl->dev_caps.dwAxes++;
|
|
if (instance->dwType & DIDFT_POV) impl->dev_caps.dwPOVs++;
|
|
|
|
if (instance->dwType & (DIDFT_BUTTON|DIDFT_AXIS|DIDFT_POV) &&
|
|
(instance->wUsagePage == HID_USAGE_PAGE_GENERIC ||
|
|
instance->wUsagePage == HID_USAGE_PAGE_BUTTON))
|
|
{
|
|
if (!impl->device_state_report_id)
|
|
impl->device_state_report_id = instance->wReportId;
|
|
else if (impl->device_state_report_id != instance->wReportId)
|
|
FIXME( "multiple device state reports found!\n" );
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL init_data_format( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
DIDATAFORMAT *format = impl->base.data_format.wine_df;
|
|
DIOBJECTDATAFORMAT *obj_format;
|
|
DWORD *index = data;
|
|
|
|
obj_format = format->rgodf + *index;
|
|
obj_format->pguid = object_usage_to_guid( instance->wUsagePage, instance->wUsage );
|
|
obj_format->dwOfs = instance->dwOfs;
|
|
obj_format->dwType = instance->dwType;
|
|
obj_format->dwFlags = instance->dwFlags;
|
|
(*index)++;
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL init_pid_reports( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct pid_control_report *device_control = &impl->pid_device_control;
|
|
struct pid_control_report *effect_control = &impl->pid_effect_control;
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
|
|
#define SET_COLLECTION( rep ) \
|
|
do \
|
|
{ \
|
|
if (rep->collection) FIXME( "duplicate " #rep " report!\n" ); \
|
|
else rep->collection = DIDFT_GETINSTANCE( instance->dwType ); \
|
|
} while (0)
|
|
|
|
#define SET_SUB_COLLECTION( rep, sub ) \
|
|
do { \
|
|
if (instance->wCollectionNumber != rep->collection) \
|
|
FIXME( "unexpected " #rep "." #sub " parent!\n" ); \
|
|
else if (rep->sub) \
|
|
FIXME( "duplicate " #rep "." #sub " collection!\n" ); \
|
|
else \
|
|
rep->sub = DIDFT_GETINSTANCE( instance->dwType ); \
|
|
} while (0)
|
|
|
|
if (instance->wUsagePage == HID_USAGE_PAGE_PID)
|
|
{
|
|
switch (instance->wUsage)
|
|
{
|
|
case PID_USAGE_DEVICE_CONTROL_REPORT: SET_COLLECTION( device_control ); break;
|
|
case PID_USAGE_EFFECT_OPERATION_REPORT: SET_COLLECTION( effect_control ); break;
|
|
case PID_USAGE_SET_EFFECT_REPORT: SET_COLLECTION( effect_update ); break;
|
|
|
|
case PID_USAGE_DEVICE_CONTROL: SET_SUB_COLLECTION( device_control, control_coll ); break;
|
|
case PID_USAGE_EFFECT_OPERATION: SET_SUB_COLLECTION( effect_control, control_coll ); break;
|
|
case PID_USAGE_EFFECT_TYPE:
|
|
if (instance->wCollectionNumber == effect_update->collection)
|
|
SET_SUB_COLLECTION( effect_update, type_coll );
|
|
break;
|
|
case PID_USAGE_AXES_ENABLE: SET_SUB_COLLECTION( effect_update, axes_coll ); break;
|
|
}
|
|
}
|
|
|
|
#undef SET_SUB_COLLECTION
|
|
#undef SET_COLLECTION
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static BOOL init_pid_caps( struct hid_joystick *impl, struct hid_value_caps *caps,
|
|
DIDEVICEOBJECTINSTANCEW *instance, void *data )
|
|
{
|
|
struct pid_control_report *device_control = &impl->pid_device_control;
|
|
struct pid_control_report *effect_control = &impl->pid_effect_control;
|
|
struct pid_effect_update *effect_update = &impl->pid_effect_update;
|
|
|
|
if (!(instance->dwType & DIDFT_OUTPUT)) return DIENUM_CONTINUE;
|
|
|
|
#define SET_REPORT_ID( rep ) \
|
|
do \
|
|
{ \
|
|
if (!rep->id) \
|
|
rep->id = instance->wReportId; \
|
|
else if (rep->id != instance->wReportId) \
|
|
FIXME( "multiple " #rep " report ids!\n" ); \
|
|
} while (0)
|
|
|
|
if (instance->wCollectionNumber == device_control->control_coll)
|
|
SET_REPORT_ID( device_control );
|
|
if (instance->wCollectionNumber == effect_control->control_coll)
|
|
SET_REPORT_ID( effect_control );
|
|
if (instance->wCollectionNumber == effect_update->type_coll)
|
|
SET_REPORT_ID( effect_update );
|
|
if (instance->wCollectionNumber == effect_update->collection)
|
|
{
|
|
SET_REPORT_ID( effect_update );
|
|
if (instance->wUsage == PID_USAGE_DURATION)
|
|
effect_update->duration_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_GAIN)
|
|
effect_update->gain_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_SAMPLE_PERIOD)
|
|
effect_update->sample_period_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_START_DELAY)
|
|
effect_update->start_delay_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_TRIGGER_BUTTON)
|
|
effect_update->trigger_button_caps = caps;
|
|
if (instance->wUsage == PID_USAGE_TRIGGER_REPEAT_INTERVAL)
|
|
effect_update->trigger_repeat_interval_caps = caps;
|
|
}
|
|
if (instance->wCollectionNumber == effect_update->axes_coll)
|
|
{
|
|
SET_REPORT_ID( effect_update );
|
|
if (effect_update->axis_count >= 6) FIXME( "more than 6 PID axes detected\n" );
|
|
else effect_update->axis_caps[effect_update->axis_count] = caps;
|
|
effect_update->axis_count++;
|
|
}
|
|
|
|
#undef SET_REPORT_ID
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID *guid, IDirectInputDevice8W **out )
|
|
{
|
|
static const DIPROPHEADER filter =
|
|
{
|
|
.dwSize = sizeof(filter),
|
|
.dwHeaderSize = sizeof(filter),
|
|
.dwHow = DIPH_DEVICE,
|
|
};
|
|
DIDEVICEINSTANCEW instance =
|
|
{
|
|
.dwSize = sizeof(instance),
|
|
.guidProduct = *guid,
|
|
.guidInstance = *guid
|
|
};
|
|
DIPROPRANGE range =
|
|
{
|
|
.diph =
|
|
{
|
|
.dwSize = sizeof(range),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_DEVICE,
|
|
},
|
|
};
|
|
DIPROPDWORD saturation =
|
|
{
|
|
.diph =
|
|
{
|
|
.dwSize = sizeof(DIPROPDWORD),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_DEVICE,
|
|
},
|
|
};
|
|
HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)};
|
|
struct hid_preparsed_data *preparsed;
|
|
struct hid_joystick *impl = NULL;
|
|
struct extra_caps *extra;
|
|
DIDATAFORMAT *format = NULL;
|
|
USAGE_AND_PAGE *usages;
|
|
DWORD size, index;
|
|
char *buffer;
|
|
HRESULT hr;
|
|
|
|
TRACE( "dinput %p, guid %s, out %p\n", dinput, debugstr_guid( guid ), out );
|
|
|
|
*out = NULL;
|
|
instance.guidProduct.Data1 = DInput_PIDVID_Product_GUID.Data1;
|
|
instance.guidInstance.Data1 = hid_joystick_guid.Data1;
|
|
if (IsEqualGUID( &DInput_PIDVID_Product_GUID, &instance.guidProduct ))
|
|
instance.guidProduct = *guid;
|
|
else if (IsEqualGUID( &hid_joystick_guid, &instance.guidInstance ))
|
|
instance.guidInstance = *guid;
|
|
else
|
|
return DIERR_DEVICENOTREG;
|
|
|
|
hr = direct_input_device_alloc( sizeof(struct hid_joystick), &hid_joystick_vtbl, guid,
|
|
dinput, (void **)&impl );
|
|
if (FAILED(hr)) return hr;
|
|
impl->base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": hid_joystick.base.crit");
|
|
impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
|
|
impl->base.read_event = CreateEventA( NULL, FALSE, FALSE, NULL );
|
|
impl->base.read_callback = hid_joystick_read_state;
|
|
|
|
hr = hid_joystick_device_open( -1, &instance, impl->device_path, &impl->device, &impl->preparsed,
|
|
&attrs, &impl->caps, dinput->dwVersion );
|
|
if (hr != DI_OK) goto failed;
|
|
|
|
impl->ref = 1;
|
|
impl->instance = instance;
|
|
impl->attrs = attrs;
|
|
impl->dev_caps.dwSize = sizeof(impl->dev_caps);
|
|
impl->dev_caps.dwFlags = DIDC_ATTACHED | DIDC_EMULATED;
|
|
impl->dev_caps.dwDevType = instance.dwDevType;
|
|
list_init( &impl->effect_list );
|
|
|
|
preparsed = (struct hid_preparsed_data *)impl->preparsed;
|
|
|
|
size = preparsed->input_caps_count * sizeof(struct extra_caps);
|
|
if (!(extra = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, size ))) goto failed;
|
|
impl->input_extra_caps = extra;
|
|
|
|
size = impl->caps.InputReportByteLength;
|
|
if (!(buffer = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
|
|
impl->input_report_buf = buffer;
|
|
size = impl->caps.OutputReportByteLength;
|
|
if (!(buffer = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
|
|
impl->output_report_buf = buffer;
|
|
impl->usages_count = HidP_MaxUsageListLength( HidP_Input, 0, impl->preparsed );
|
|
size = impl->usages_count * sizeof(USAGE_AND_PAGE);
|
|
if (!(usages = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
|
|
impl->usages_buf = usages;
|
|
|
|
enum_objects( impl, &filter, DIDFT_ALL, init_objects, NULL );
|
|
enum_objects( impl, &filter, DIDFT_COLLECTION, init_pid_reports, NULL );
|
|
enum_objects( impl, &filter, DIDFT_NODATA, init_pid_caps, NULL );
|
|
|
|
TRACE( "device control id %u, coll %u, control coll %u\n", impl->pid_device_control.id,
|
|
impl->pid_device_control.collection, impl->pid_device_control.control_coll );
|
|
TRACE( "effect control id %u, coll %u\n", impl->pid_effect_control.id, impl->pid_effect_control.collection );
|
|
TRACE( "effect update id %u, coll %u, type_coll %u\n", impl->pid_effect_update.id,
|
|
impl->pid_effect_update.collection, impl->pid_effect_update.type_coll );
|
|
|
|
if (impl->pid_device_control.id)
|
|
{
|
|
impl->dev_caps.dwFlags |= DIDC_FORCEFEEDBACK;
|
|
if (impl->pid_effect_update.start_delay_caps)
|
|
impl->dev_caps.dwFlags |= DIDC_STARTDELAY;
|
|
impl->dev_caps.dwFFSamplePeriod = 1000000;
|
|
impl->dev_caps.dwFFMinTimeResolution = 1000000;
|
|
impl->dev_caps.dwHardwareRevision = 1;
|
|
impl->dev_caps.dwFFDriverVersion = 1;
|
|
}
|
|
|
|
format = impl->base.data_format.wine_df;
|
|
if (format->dwDataSize > DEVICE_STATE_MAX_SIZE)
|
|
{
|
|
FIXME( "unable to create device, state is too large\n" );
|
|
goto failed;
|
|
}
|
|
|
|
size = format->dwNumObjs * sizeof(*format->rgodf);
|
|
if (!(format->rgodf = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, size ))) goto failed;
|
|
format->dwSize = sizeof(*format);
|
|
format->dwObjSize = sizeof(*format->rgodf);
|
|
format->dwFlags = DIDF_ABSAXIS;
|
|
|
|
index = 0;
|
|
enum_objects( impl, &filter, DIDFT_ALL, init_data_format, &index );
|
|
|
|
_dump_DIDATAFORMAT( impl->base.data_format.wine_df );
|
|
|
|
range.lMax = 65535;
|
|
enum_objects( impl, &range.diph, DIDFT_AXIS, set_property_prop_range, &range );
|
|
range.lMax = 36000;
|
|
enum_objects( impl, &range.diph, DIDFT_POV, set_property_prop_range, &range );
|
|
saturation.dwData = 10000;
|
|
enum_objects( impl, &range.diph, DIDFT_AXIS, set_property_prop_saturation, &saturation );
|
|
|
|
*out = &impl->base.IDirectInputDevice8W_iface;
|
|
return DI_OK;
|
|
|
|
failed:
|
|
IDirectInputDevice_Release( &impl->base.IDirectInputDevice8W_iface );
|
|
return hr;
|
|
}
|
|
|
|
const struct dinput_device joystick_hid_device =
|
|
{
|
|
"Wine HID joystick driver",
|
|
hid_joystick_enum_device,
|
|
hid_joystick_create_device,
|
|
};
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_QueryInterface( IDirectInputEffect *iface, REFIID iid, void **out )
|
|
{
|
|
TRACE( "iface %p, iid %s, out %p\n", iface, debugstr_guid( iid ), out );
|
|
|
|
if (IsEqualGUID( iid, &IID_IUnknown ) ||
|
|
IsEqualGUID( iid, &IID_IDirectInputEffect ))
|
|
{
|
|
IDirectInputEffect_AddRef( iface );
|
|
*out = iface;
|
|
return S_OK;
|
|
}
|
|
|
|
FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) );
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_effect_AddRef( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI hid_joystick_effect_Release( IDirectInputEffect *iface )
|
|
{
|
|
struct hid_joystick_effect *impl = impl_from_IDirectInputEffect( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
TRACE( "iface %p, ref %u.\n", iface, ref );
|
|
if (!ref)
|
|
{
|
|
EnterCriticalSection( &impl->joystick->base.crit );
|
|
list_remove( &impl->entry );
|
|
LeaveCriticalSection( &impl->joystick->base.crit );
|
|
hid_joystick_private_decref( impl->joystick );
|
|
HeapFree( GetProcessHeap(), 0, impl );
|
|
}
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Initialize( IDirectInputEffect *iface, HINSTANCE inst,
|
|
DWORD version, REFGUID guid )
|
|
{
|
|
FIXME( "iface %p, inst %p, version %u, guid %s stub!\n", iface, inst, version, debugstr_guid( guid ) );
|
|
return DI_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetEffectGuid( IDirectInputEffect *iface, GUID *guid )
|
|
{
|
|
FIXME( "iface %p, guid %p stub!\n", iface, guid );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetParameters( IDirectInputEffect *iface, DIEFFECT *params, DWORD flags )
|
|
{
|
|
FIXME( "iface %p, params %p, flags %#x stub!\n", iface, params, flags );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_SetParameters( IDirectInputEffect *iface,
|
|
const DIEFFECT *params, DWORD flags )
|
|
{
|
|
FIXME( "iface %p, params %p, flags %#x stub!\n", iface, params, flags );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Start( IDirectInputEffect *iface, DWORD iterations, DWORD flags )
|
|
{
|
|
FIXME( "iface %p, iterations %u, flags %#x stub!\n", iface, iterations, flags );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Stop( IDirectInputEffect *iface )
|
|
{
|
|
FIXME( "iface %p stub!\n", iface );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_GetEffectStatus( IDirectInputEffect *iface, DWORD *status )
|
|
{
|
|
FIXME( "iface %p, status %p stub!\n", iface, status );
|
|
|
|
if (!status) return E_POINTER;
|
|
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Download( IDirectInputEffect *iface )
|
|
{
|
|
FIXME( "iface %p stub!\n", iface );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Unload( IDirectInputEffect *iface )
|
|
{
|
|
FIXME( "iface %p stub!\n", iface );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static HRESULT WINAPI hid_joystick_effect_Escape( IDirectInputEffect *iface, DIEFFESCAPE *escape )
|
|
{
|
|
FIXME( "iface %p, escape %p stub!\n", iface, escape );
|
|
return DIERR_UNSUPPORTED;
|
|
}
|
|
|
|
static IDirectInputEffectVtbl hid_joystick_effect_vtbl =
|
|
{
|
|
/*** IUnknown methods ***/
|
|
hid_joystick_effect_QueryInterface,
|
|
hid_joystick_effect_AddRef,
|
|
hid_joystick_effect_Release,
|
|
/*** IDirectInputEffect methods ***/
|
|
hid_joystick_effect_Initialize,
|
|
hid_joystick_effect_GetEffectGuid,
|
|
hid_joystick_effect_GetParameters,
|
|
hid_joystick_effect_SetParameters,
|
|
hid_joystick_effect_Start,
|
|
hid_joystick_effect_Stop,
|
|
hid_joystick_effect_GetEffectStatus,
|
|
hid_joystick_effect_Download,
|
|
hid_joystick_effect_Unload,
|
|
hid_joystick_effect_Escape,
|
|
};
|
|
|
|
static HRESULT hid_joystick_effect_create( struct hid_joystick *joystick, IDirectInputEffect **out )
|
|
{
|
|
struct hid_joystick_effect *impl;
|
|
|
|
if (!(impl = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*impl) )))
|
|
return DIERR_OUTOFMEMORY;
|
|
impl->IDirectInputEffect_iface.lpVtbl = &hid_joystick_effect_vtbl;
|
|
impl->ref = 1;
|
|
impl->joystick = joystick;
|
|
hid_joystick_private_incref( joystick );
|
|
|
|
EnterCriticalSection( &joystick->base.crit );
|
|
list_add_tail( &joystick->effect_list, &impl->entry );
|
|
LeaveCriticalSection( &joystick->base.crit );
|
|
|
|
*out = &impl->IDirectInputEffect_iface;
|
|
return DI_OK;
|
|
}
|