dinput: Enumerate HID collections, input buttons and values.

And initialize data format from them.

Signed-off-by: Rémi Bernon <rbernon@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Rémi Bernon 2021-08-27 12:45:25 +02:00 committed by Alexandre Julliard
parent 10a812fb7d
commit 81c15d2d81
1 changed files with 415 additions and 2 deletions

View File

@ -48,6 +48,93 @@ WINE_DEFAULT_DEBUG_CHANNEL(dinput);
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_hidp_link_collection_node( HIDP_LINK_COLLECTION_NODE *node )
{
if (!node) return "(null)";
return wine_dbg_sprintf( "Usg %02x:%02x Par %u Nxt %u Cnt %u Chld %u Type %u Alias %d User %p",
node->LinkUsagePage, node->LinkUsage, node->Parent, node->NextSibling,
node->NumberOfChildren, node->FirstChild, node->CollectionType, node->IsAlias,
node->UserContext );
}
static inline const char *debugstr_hidp_button_caps( HIDP_BUTTON_CAPS *caps )
{
const char *str;
if (!caps) return "(null)";
str = wine_dbg_sprintf( "RId %d,", caps->ReportID );
if (!caps->IsRange)
str = wine_dbg_sprintf( "%s Usg %02x:%02x Dat %02x,", str, caps->UsagePage, caps->NotRange.Usage,
caps->NotRange.DataIndex );
else
str = wine_dbg_sprintf( "%s Usg %02x:%02x-%02x Dat %02x-%02x,", str, caps->UsagePage, caps->Range.UsageMin,
caps->Range.UsageMax, caps->Range.DataIndexMin, caps->Range.DataIndexMax );
if (!caps->IsStringRange)
str = wine_dbg_sprintf( "%s Str %d,", str, caps->NotRange.StringIndex );
else
str = wine_dbg_sprintf( "%s Str %d-%d,", str, caps->Range.StringMin, caps->Range.StringMax );
if (!caps->IsDesignatorRange)
str = wine_dbg_sprintf( "%s Des %d,", str, caps->NotRange.DesignatorIndex );
else
str = wine_dbg_sprintf( "%s Des %d-%d,", str, caps->Range.DesignatorMin, caps->Range.DesignatorMax );
return wine_dbg_sprintf( "%s Bits %02x Alias %d Abs %d, LCol %u LUsg %02x-%02x", str, caps->BitField, caps->IsAlias,
caps->IsAbsolute, caps->LinkCollection, caps->LinkUsagePage, caps->LinkUsage );
}
static inline const char *debugstr_hidp_value_caps( HIDP_VALUE_CAPS *caps )
{
const char *str;
if (!caps) return "(null)";
str = wine_dbg_sprintf( "RId %d,", caps->ReportID );
if (!caps->IsRange)
str = wine_dbg_sprintf( "%s Usg %02x:%02x Dat %02x,", str, caps->UsagePage, caps->NotRange.Usage,
caps->NotRange.DataIndex );
else
str = wine_dbg_sprintf( "%s Usg %02x:%02x-%02x Dat %02x-%02x,", str, caps->UsagePage, caps->Range.UsageMin,
caps->Range.UsageMax, caps->Range.DataIndexMin, caps->Range.DataIndexMax );
if (!caps->IsStringRange)
str = wine_dbg_sprintf( "%s Str %d,", str, caps->NotRange.StringIndex );
else
str = wine_dbg_sprintf( "%s Str %d-%d,", str, caps->Range.StringMin, caps->Range.StringMax );
if (!caps->IsDesignatorRange)
str = wine_dbg_sprintf( "%s Des %d,", str, caps->NotRange.DesignatorIndex );
else
str = wine_dbg_sprintf( "%s Des %d-%d,", str, caps->Range.DesignatorMin, caps->Range.DesignatorMax );
return wine_dbg_sprintf( "%s Bits %02x Alias %d Abs %d Null %d, LCol %u LUsg %02x-%02x, "
"BitSz %d, RCnt %d, Unit %x E%+d, Log %+d-%+d, Phy %+d-%+d",
str, caps->BitField, caps->IsAlias, caps->IsAbsolute, caps->HasNull,
caps->LinkCollection, caps->LinkUsagePage, caps->LinkUsage,
caps->BitSize, caps->ReportCount, caps->Units, caps->UnitsExp,
caps->LogicalMin, caps->LogicalMax, caps->PhysicalMin, caps->PhysicalMax );
}
struct hid_caps
{
enum { LINK_COLLECTION_NODE, BUTTON_CAPS, VALUE_CAPS } type;
union
{
HIDP_LINK_COLLECTION_NODE *node;
HIDP_BUTTON_CAPS *button;
HIDP_VALUE_CAPS *value;
};
};
static inline const char *debugstr_hid_caps( struct hid_caps *caps )
{
switch (caps->type)
{
case LINK_COLLECTION_NODE:
return debugstr_hidp_link_collection_node( caps->node );
case BUTTON_CAPS:
return debugstr_hidp_button_caps( caps->button );
case VALUE_CAPS:
return debugstr_hidp_value_caps( caps->value );
}
return "(unknown type)";
}
struct hid_joystick
{
IDirectInputDeviceImpl base;
@ -60,6 +147,11 @@ struct hid_joystick
WCHAR device_path[MAX_PATH];
HIDD_ATTRIBUTES attrs;
DIDEVCAPS dev_caps;
HIDP_CAPS caps;
HIDP_LINK_COLLECTION_NODE *collection_nodes;
HIDP_BUTTON_CAPS *input_button_caps;
HIDP_VALUE_CAPS *input_value_caps;
};
static inline struct hid_joystick *impl_from_IDirectInputDevice8W( IDirectInputDevice8W *iface )
@ -68,6 +160,242 @@ static inline struct hid_joystick *impl_from_IDirectInputDevice8W( IDirectInputD
struct hid_joystick, base );
}
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_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;
}
typedef BOOL (*enum_object_callback)( struct hid_joystick *impl, struct hid_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_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 (LOWORD(filter->dwObj) != instance->wUsagePage || HIWORD(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 BOOL enum_value_objects( struct hid_joystick *impl, const DIPROPHEADER *filter,
DWORD flags, enum_object_callback callback, void *data )
{
DIDEVICEOBJECTINSTANCEW instance = {.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW)};
struct hid_caps caps = {.type = VALUE_CAPS};
DWORD axis = 0, pov = 0, i;
BOOL ret;
for (i = 0; i < impl->caps.NumberInputValueCaps; ++i)
{
caps.value = impl->input_value_caps + i;
if (caps.value->UsagePage >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
TRACE( "Ignoring input value %s, vendor specific.\n", debugstr_hid_caps( &caps ) );
else if (caps.value->IsAlias)
TRACE( "Ignoring input value %s, aliased.\n", debugstr_hid_caps( &caps ) );
else if (caps.value->IsRange)
FIXME( "Ignoring input value %s, usage range not implemented.\n", debugstr_hid_caps( &caps ) );
else if (caps.value->ReportCount > 1)
FIXME( "Ignoring input value %s, array not implemented.\n", debugstr_hid_caps( &caps ) );
else if (caps.value->UsagePage != HID_USAGE_PAGE_GENERIC)
TRACE( "Ignoring input value %s, usage page not implemented.\n", debugstr_hid_caps( &caps ) );
else
{
instance.wUsagePage = caps.value->UsagePage;
instance.wUsage = caps.value->NotRange.Usage;
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
instance.wReportId = caps.value->ReportID;
switch (instance.wUsage)
{
case HID_USAGE_GENERIC_X:
instance.dwOfs = DIJOFS_X;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_Y:
instance.dwOfs = DIJOFS_Y;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_Z:
instance.dwOfs = DIJOFS_Z;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_RX:
instance.dwOfs = DIJOFS_RX;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_RY:
instance.dwOfs = DIJOFS_RY;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_RZ:
instance.dwOfs = DIJOFS_RZ;
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_SLIDER:
instance.dwOfs = DIJOFS_SLIDER( 0 );
instance.dwType = DIDFT_ABSAXIS | DIDFT_MAKEINSTANCE( axis++ );
instance.dwFlags = DIDOI_ASPECTPOSITION;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
case HID_USAGE_GENERIC_HATSWITCH:
instance.dwOfs = DIJOFS_POV( 0 );
instance.dwType = DIDFT_POV | DIDFT_MAKEINSTANCE( pov++ );
instance.dwFlags = 0;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
break;
default:
FIXME( "Ignoring input value %s, usage not implemented.\n", debugstr_hid_caps( &caps ) );
break;
}
}
}
return DIENUM_CONTINUE;
}
static BOOL enum_button_objects( struct hid_joystick *impl, const DIPROPHEADER *filter,
DWORD flags, enum_object_callback callback, void *data )
{
DIDEVICEOBJECTINSTANCEW instance = {.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW)};
struct hid_caps caps = {.type = BUTTON_CAPS};
DWORD button = 0, i, j;
BOOL ret;
for (i = 0; i < impl->caps.NumberInputButtonCaps; ++i)
{
caps.button = impl->input_button_caps + i;
if (caps.button->UsagePage >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
TRACE( "Ignoring input button %s, vendor specific.\n", debugstr_hid_caps( &caps ) );
else if (caps.button->IsAlias)
TRACE( "Ignoring input button %s, aliased.\n", debugstr_hid_caps( &caps ) );
else if (caps.button->UsagePage != HID_USAGE_PAGE_BUTTON)
TRACE( "Ignoring input button %s, usage page not implemented.\n", debugstr_hid_caps( &caps ) );
else if (caps.button->IsRange)
{
if (caps.button->NotRange.Usage >= 128)
FIXME( "Ignoring input button %s, too many buttons.\n", debugstr_hid_caps( &caps ) );
else for (j = caps.button->Range.UsageMin; j <= caps.button->Range.UsageMax; ++j)
{
instance.dwOfs = DIJOFS_BUTTON( j - 1 );
instance.dwType = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE( button++ );
instance.dwFlags = 0;
instance.wUsagePage = caps.button->UsagePage;
instance.wUsage = j;
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
instance.wReportId = caps.button->ReportID;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
}
}
else if (caps.button->NotRange.Usage >= 128)
FIXME( "Ignoring input button %s, too many buttons.\n", debugstr_hid_caps( &caps ) );
else
{
instance.dwOfs = DIJOFS_BUTTON( caps.button->NotRange.Usage - 1 );
instance.dwType = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE( button++ );
instance.dwFlags = 0;
instance.wUsagePage = caps.button->UsagePage;
instance.wUsage = caps.button->NotRange.Usage;
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
instance.wReportId = caps.button->ReportID;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
}
}
return DIENUM_CONTINUE;
}
static BOOL enum_collections_objects( struct hid_joystick *impl, const DIPROPHEADER *filter, DWORD flags,
enum_object_callback callback, void *data )
{
DIDEVICEOBJECTINSTANCEW instance = {.dwSize = sizeof(DIDEVICEOBJECTINSTANCEW)};
struct hid_caps caps = {.type = LINK_COLLECTION_NODE};
BOOL ret;
DWORD i;
for (i = 0; i < impl->caps.NumberLinkCollectionNodes; ++i)
{
caps.node = impl->collection_nodes + i;
if (caps.node->LinkUsagePage >= HID_USAGE_PAGE_VENDOR_DEFINED_BEGIN)
TRACE( "Ignoring collection %s, vendor specific.\n", debugstr_hid_caps( &caps ) );
else if (caps.node->IsAlias)
TRACE( "Ignoring collection %s, aliased.\n", debugstr_hid_caps( &caps ) );
else
{
instance.dwOfs = 0;
instance.dwType = DIDFT_COLLECTION | DIDFT_NODATA;
instance.dwFlags = 0;
instance.wUsagePage = caps.node->LinkUsagePage;
instance.wUsage = caps.node->LinkUsage;
instance.guidType = *object_usage_to_guid( instance.wUsagePage, instance.wUsage );
instance.wReportId = 0;
ret = enum_object( impl, filter, flags, callback, &caps, &instance, data );
if (ret != DIENUM_CONTINUE) return ret;
}
}
return DIENUM_CONTINUE;
}
static ULONG WINAPI hid_joystick_Release( IDirectInputDevice8W *iface )
{
struct hid_joystick *impl = impl_from_IDirectInputDevice8W( iface );
@ -76,6 +404,9 @@ static ULONG WINAPI hid_joystick_Release( IDirectInputDevice8W *iface )
if (!(ref = IDirectInputDevice2WImpl_Release( iface )))
{
HeapFree( GetProcessHeap(), 0, tmp.input_value_caps );
HeapFree( GetProcessHeap(), 0, tmp.input_button_caps );
HeapFree( GetProcessHeap(), 0, tmp.collection_nodes );
HidD_FreePreparsedData( tmp.preparsed );
CloseHandle( tmp.device );
}
@ -372,8 +703,44 @@ static HRESULT hid_joystick_enum_device( DWORD type, DWORD flags, DIDEVICEINSTAN
return DI_OK;
}
static BOOL init_objects( struct hid_joystick *impl, struct hid_caps *caps,
DIDEVICEOBJECTINSTANCEW *instance, void *data )
{
DIDATAFORMAT *format = impl->base.data_format.wine_df;
format->dwNumObjs++;
if (instance->dwType & DIDFT_PSHBUTTON) impl->dev_caps.dwButtons++;
if (instance->dwType & DIDFT_AXIS) impl->dev_caps.dwAxes++;
if (instance->dwType & DIDFT_POV) impl->dev_caps.dwPOVs++;
return DIENUM_CONTINUE;
}
static BOOL init_data_format( struct hid_joystick *impl, struct hid_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 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),
@ -381,8 +748,13 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
.guidInstance = *guid
};
HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)};
HIDP_LINK_COLLECTION_NODE *nodes;
struct hid_joystick *impl = NULL;
HIDP_CAPS caps;
DIDATAFORMAT *format = NULL;
HIDP_BUTTON_CAPS *buttons;
HIDP_VALUE_CAPS *values;
DWORD size, index;
NTSTATUS status;
HRESULT hr;
TRACE( "dinput %p, guid %s, out %p\n", dinput, debugstr_guid( guid ), out );
@ -404,7 +776,7 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;
hr = hid_joystick_device_open( -1, &instance, impl->device_path, &impl->device, &impl->preparsed,
&attrs, &caps, dinput->dwVersion );
&attrs, &impl->caps, dinput->dwVersion );
if (hr != DI_OK) goto failed;
impl->instance = instance;
@ -413,6 +785,47 @@ static HRESULT hid_joystick_create_device( IDirectInputImpl *dinput, const GUID
impl->dev_caps.dwFlags = DIDC_ATTACHED | DIDC_EMULATED;
impl->dev_caps.dwDevType = instance.dwDevType;
size = impl->caps.NumberLinkCollectionNodes * sizeof(HIDP_LINK_COLLECTION_NODE);
if (!(nodes = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
impl->collection_nodes = nodes;
size = impl->caps.NumberInputButtonCaps * sizeof(HIDP_BUTTON_CAPS);
if (!(buttons = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
impl->input_button_caps = buttons;
size = impl->caps.NumberInputValueCaps * sizeof(HIDP_VALUE_CAPS);
if (!(values = HeapAlloc( GetProcessHeap(), 0, size ))) goto failed;
impl->input_value_caps = values;
size = impl->caps.NumberLinkCollectionNodes;
status = HidP_GetLinkCollectionNodes( nodes, &size, impl->preparsed );
if (status != HIDP_STATUS_SUCCESS) goto failed;
impl->caps.NumberLinkCollectionNodes = size;
status = HidP_GetButtonCaps( HidP_Input, impl->input_button_caps,
&impl->caps.NumberInputButtonCaps, impl->preparsed );
if (status != HIDP_STATUS_SUCCESS && status != HIDP_STATUS_USAGE_NOT_FOUND) goto failed;
status = HidP_GetValueCaps( HidP_Input, impl->input_value_caps,
&impl->caps.NumberInputValueCaps, impl->preparsed );
if (status != HIDP_STATUS_SUCCESS && status != HIDP_STATUS_USAGE_NOT_FOUND) goto failed;
/* enumerate collections first, so we can find report collections */
enum_collections_objects( impl, &filter, DIDFT_ALL, init_objects, NULL );
enum_value_objects( impl, &filter, DIDFT_ALL, init_objects, NULL );
enum_button_objects( impl, &filter, DIDFT_ALL, init_objects, NULL );
format = impl->base.data_format.wine_df;
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;
format->dwDataSize = sizeof(impl->state);
index = 0;
enum_value_objects( impl, &filter, DIDFT_ALL, init_data_format, &index );
enum_button_objects( impl, &filter, DIDFT_ALL, init_data_format, &index );
enum_collections_objects( impl, &filter, DIDFT_ALL, init_data_format, &index );
_dump_DIDATAFORMAT( impl->base.data_format.wine_df );
*out = &impl->base.IDirectInputDevice8W_iface;
return DI_OK;