mirror of
git://source.winehq.org/git/wine.git
synced 2024-09-01 18:23:38 +00:00
![Ivo Ivanov](/assets/img/avatar_default.png)
The HID PID steering wheels always declare one force feedback axis, while the joysticks always have two or more. So it is safe to assume that joysticks with single FFB axis are racing wheels. Fixes FH5 not having force feedback with a Simucube 2 steering wheel, when using the hidraw backend.
621 lines
24 KiB
C
621 lines
24 KiB
C
/* WinRT Windows.Gaming.Input implementation
|
|
*
|
|
* Copyright 2022 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 "private.h"
|
|
|
|
#include "initguid.h"
|
|
#include "ddk/hidsdi.h"
|
|
#include "dinput.h"
|
|
#include "provider.h"
|
|
#include "hidusage.h"
|
|
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(input);
|
|
|
|
DEFINE_GUID( device_path_guid, 0x00000000, 0x0000, 0x0000, 0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf8 );
|
|
|
|
static CRITICAL_SECTION provider_cs;
|
|
static CRITICAL_SECTION_DEBUG provider_cs_debug =
|
|
{
|
|
0, 0, &provider_cs,
|
|
{ &provider_cs_debug.ProcessLocksList, &provider_cs_debug.ProcessLocksList },
|
|
0, 0, { (DWORD_PTR)(__FILE__ ": provider_cs") }
|
|
};
|
|
static CRITICAL_SECTION provider_cs = { &provider_cs_debug, -1, 0, 0, 0, 0 };
|
|
|
|
static struct list provider_list = LIST_INIT( provider_list );
|
|
|
|
struct provider
|
|
{
|
|
IWineGameControllerProvider IWineGameControllerProvider_iface;
|
|
IGameControllerProvider IGameControllerProvider_iface;
|
|
LONG ref;
|
|
|
|
IDirectInputDevice8W *dinput_device;
|
|
WCHAR device_path[MAX_PATH];
|
|
struct list entry;
|
|
|
|
struct WineGameControllerVibration vibration;
|
|
|
|
char *report_buf;
|
|
PHIDP_PREPARSED_DATA preparsed;
|
|
HIDP_VALUE_CAPS haptics_rumble_caps;
|
|
HIDP_VALUE_CAPS haptics_buzz_caps;
|
|
HIDP_VALUE_CAPS haptics_left_caps;
|
|
HIDP_VALUE_CAPS haptics_right_caps;
|
|
BYTE haptics_report;
|
|
HIDP_CAPS caps;
|
|
HANDLE device;
|
|
};
|
|
|
|
static inline struct provider *impl_from_IWineGameControllerProvider( IWineGameControllerProvider *iface )
|
|
{
|
|
return CONTAINING_RECORD( iface, struct provider, IWineGameControllerProvider_iface );
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_QueryInterface( IWineGameControllerProvider *iface, REFIID iid, void **out )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
|
|
TRACE( "iface %p, iid %s, out %p.\n", iface, debugstr_guid( iid ), out );
|
|
|
|
if (IsEqualGUID( iid, &IID_IUnknown ) ||
|
|
IsEqualGUID( iid, &IID_IInspectable ) ||
|
|
IsEqualGUID( iid, &IID_IAgileObject ) ||
|
|
IsEqualGUID( iid, &IID_IWineGameControllerProvider ))
|
|
{
|
|
IInspectable_AddRef( (*out = &impl->IWineGameControllerProvider_iface) );
|
|
return S_OK;
|
|
}
|
|
|
|
if (IsEqualGUID( iid, &IID_IGameControllerProvider ))
|
|
{
|
|
IInspectable_AddRef( (*out = &impl->IGameControllerProvider_iface) );
|
|
return S_OK;
|
|
}
|
|
|
|
FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) );
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI wine_provider_AddRef( IWineGameControllerProvider *iface )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p increasing refcount to %lu.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI wine_provider_Release( IWineGameControllerProvider *iface )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
|
|
TRACE( "iface %p decreasing refcount to %lu.\n", iface, ref );
|
|
|
|
if (!ref)
|
|
{
|
|
IDirectInputDevice8_Release( impl->dinput_device );
|
|
HidD_FreePreparsedData( impl->preparsed );
|
|
CloseHandle( impl->device );
|
|
free( impl->report_buf );
|
|
free( impl );
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_GetIids( IWineGameControllerProvider *iface, ULONG *iid_count, IID **iids )
|
|
{
|
|
FIXME( "iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_GetRuntimeClassName( IWineGameControllerProvider *iface, HSTRING *class_name )
|
|
{
|
|
FIXME( "iface %p, class_name %p stub!\n", iface, class_name );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_GetTrustLevel( IWineGameControllerProvider *iface, TrustLevel *trust_level )
|
|
{
|
|
FIXME( "iface %p, trust_level %p stub!\n", iface, trust_level );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static BOOL CALLBACK count_ffb_axes( const DIDEVICEOBJECTINSTANCEW *obj, void *args )
|
|
{
|
|
DWORD *count = args;
|
|
if (obj->dwType & DIDFT_FFACTUATOR) (*count)++;
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_Type( IWineGameControllerProvider *iface, WineGameControllerType *value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIDEVICEINSTANCEW instance = {.dwSize = sizeof(DIDEVICEINSTANCEW)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_GetDeviceInfo( impl->dinput_device, &instance ))) return hr;
|
|
|
|
switch (GET_DIDEVICE_TYPE( instance.dwDevType ))
|
|
{
|
|
case DI8DEVTYPE_DRIVING: *value = WineGameControllerType_RacingWheel; break;
|
|
case DI8DEVTYPE_GAMEPAD: *value = WineGameControllerType_Gamepad; break;
|
|
default:
|
|
{
|
|
DWORD count = 0;
|
|
hr = IDirectInputDevice8_EnumObjects( impl->dinput_device, count_ffb_axes, &count, DIDFT_AXIS );
|
|
if (SUCCEEDED(hr) && count == 1) *value = WineGameControllerType_RacingWheel;
|
|
else *value = WineGameControllerType_Joystick;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_AxisCount( IWineGameControllerProvider *iface, INT32 *value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetCapabilities( impl->dinput_device, &caps )))
|
|
*value = caps.dwAxes;
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_ButtonCount( IWineGameControllerProvider *iface, INT32 *value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetCapabilities( impl->dinput_device, &caps )))
|
|
*value = caps.dwButtons;
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_SwitchCount( IWineGameControllerProvider *iface, INT32 *value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetCapabilities( impl->dinput_device, &caps )))
|
|
*value = caps.dwPOVs;
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_State( IWineGameControllerProvider *iface, struct WineGameControllerState *out )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIJOYSTATE2 state = {0};
|
|
UINT32 i = 0;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, out %p.\n", iface, out );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_GetDeviceState( impl->dinput_device, sizeof(state), &state )))
|
|
{
|
|
WARN( "Failed to read device state, hr %#lx\n", hr );
|
|
return S_OK;
|
|
}
|
|
|
|
i = ARRAY_SIZE(state.rgbButtons);
|
|
while (i--) out->buttons[i] = (state.rgbButtons[i] != 0);
|
|
|
|
i = ARRAY_SIZE(state.rgdwPOV);
|
|
while (i--)
|
|
{
|
|
if (state.rgdwPOV[i] == ~0) out->switches[i] = GameControllerSwitchPosition_Center;
|
|
else out->switches[i] = state.rgdwPOV[i] * 8 / 36000 + 1;
|
|
}
|
|
|
|
i = 0;
|
|
out->axes[i++] = state.lX / 65535.;
|
|
out->axes[i++] = state.lY / 65535.;
|
|
out->axes[i++] = state.lZ / 65535.;
|
|
out->axes[i++] = state.lRx / 65535.;
|
|
out->axes[i++] = state.lRy / 65535.;
|
|
out->axes[i++] = state.lRz / 65535.;
|
|
out->axes[i++] = state.rglSlider[0] / 65535.;
|
|
out->axes[i++] = state.rglSlider[1] / 65535.;
|
|
out->axes[i++] = state.lVX / 65535.;
|
|
out->axes[i++] = state.lVY / 65535.;
|
|
out->axes[i++] = state.lVZ / 65535.;
|
|
out->axes[i++] = state.lVRx / 65535.;
|
|
out->axes[i++] = state.lVRy / 65535.;
|
|
out->axes[i++] = state.lVRz / 65535.;
|
|
out->axes[i++] = state.rglVSlider[0] / 65535.;
|
|
out->axes[i++] = state.rglVSlider[1] / 65535.;
|
|
out->axes[i++] = state.lAX / 65535.;
|
|
out->axes[i++] = state.lAY / 65535.;
|
|
out->axes[i++] = state.lAZ / 65535.;
|
|
out->axes[i++] = state.lARx / 65535.;
|
|
out->axes[i++] = state.lARy / 65535.;
|
|
out->axes[i++] = state.lARz / 65535.;
|
|
out->axes[i++] = state.rglASlider[0] / 65535.;
|
|
out->axes[i++] = state.rglASlider[1] / 65535.;
|
|
out->axes[i++] = state.lFX / 65535.;
|
|
out->axes[i++] = state.lFY / 65535.;
|
|
out->axes[i++] = state.lFZ / 65535.;
|
|
out->axes[i++] = state.lFRx / 65535.;
|
|
out->axes[i++] = state.lFRy / 65535.;
|
|
out->axes[i++] = state.lFRz / 65535.;
|
|
out->axes[i++] = state.rglFSlider[0] / 65535.;
|
|
out->axes[i++] = state.rglFSlider[1] / 65535.;
|
|
out->timestamp = GetTickCount64();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_Vibration( IWineGameControllerProvider *iface, struct WineGameControllerVibration *out )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
TRACE( "iface %p, out %p.\n", iface, out );
|
|
*out = impl->vibration;
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_put_Vibration( IWineGameControllerProvider *iface, struct WineGameControllerVibration value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
ULONG report_len = impl->caps.OutputReportByteLength;
|
|
PHIDP_PREPARSED_DATA preparsed = impl->preparsed;
|
|
char *report_buf = impl->report_buf;
|
|
USHORT collection;
|
|
NTSTATUS status;
|
|
BOOL ret;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, &value );
|
|
|
|
if (!memcmp( &impl->vibration, &value, sizeof(value) )) return S_OK;
|
|
impl->vibration = value;
|
|
|
|
status = HidP_InitializeReportForID( HidP_Output, impl->haptics_report, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_InitializeReportForID returned %#lx\n", status );
|
|
|
|
collection = impl->haptics_rumble_caps.LinkCollection;
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY,
|
|
impl->vibration.rumble, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status );
|
|
|
|
collection = impl->haptics_buzz_caps.LinkCollection;
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY,
|
|
impl->vibration.buzz, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status );
|
|
|
|
collection = impl->haptics_left_caps.LinkCollection;
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY,
|
|
impl->vibration.left, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status );
|
|
|
|
collection = impl->haptics_right_caps.LinkCollection;
|
|
status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY,
|
|
impl->vibration.right, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status );
|
|
|
|
ret = HidD_SetOutputReport( impl->device, report_buf, report_len );
|
|
if (!ret) WARN( "HidD_SetOutputReport failed with error %lu\n", GetLastError() );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI wine_provider_get_ForceFeedbackMotor( IWineGameControllerProvider *iface, IForceFeedbackMotor **value )
|
|
{
|
|
struct provider *impl = impl_from_IWineGameControllerProvider( iface );
|
|
DIDEVCAPS caps = {.dwSize = sizeof(DIDEVCAPS)};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetCapabilities( impl->dinput_device, &caps )) && (caps.dwFlags & DIDC_FORCEFEEDBACK))
|
|
return force_feedback_motor_create( impl->dinput_device, value );
|
|
|
|
*value = NULL;
|
|
return S_OK;
|
|
}
|
|
|
|
static const struct IWineGameControllerProviderVtbl wine_provider_vtbl =
|
|
{
|
|
wine_provider_QueryInterface,
|
|
wine_provider_AddRef,
|
|
wine_provider_Release,
|
|
/* IInspectable methods */
|
|
wine_provider_GetIids,
|
|
wine_provider_GetRuntimeClassName,
|
|
wine_provider_GetTrustLevel,
|
|
/* IWineGameControllerProvider methods */
|
|
wine_provider_get_Type,
|
|
wine_provider_get_AxisCount,
|
|
wine_provider_get_ButtonCount,
|
|
wine_provider_get_SwitchCount,
|
|
wine_provider_get_State,
|
|
wine_provider_get_Vibration,
|
|
wine_provider_put_Vibration,
|
|
wine_provider_get_ForceFeedbackMotor,
|
|
};
|
|
|
|
DEFINE_IINSPECTABLE( game_provider, IGameControllerProvider, struct provider, IWineGameControllerProvider_iface )
|
|
|
|
static HRESULT WINAPI game_provider_get_FirmwareVersionInfo( IGameControllerProvider *iface, GameControllerVersionInfo *value )
|
|
{
|
|
FIXME( "iface %p, value %p stub!\n", iface, value );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI game_provider_get_HardwareProductId( IGameControllerProvider *iface, UINT16 *value )
|
|
{
|
|
DIPROPDWORD vid_pid = {.diph = {.dwHeaderSize = sizeof(DIPROPHEADER), .dwSize = sizeof(DIPROPDWORD)}};
|
|
struct provider *impl = impl_from_IGameControllerProvider( iface );
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetProperty( impl->dinput_device, DIPROP_VIDPID, &vid_pid.diph )))
|
|
*value = HIWORD(vid_pid.dwData);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI game_provider_get_HardwareVendorId( IGameControllerProvider *iface, UINT16 *value )
|
|
{
|
|
DIPROPDWORD vid_pid = {.diph = {.dwHeaderSize = sizeof(DIPROPHEADER), .dwSize = sizeof(DIPROPDWORD)}};
|
|
struct provider *impl = impl_from_IGameControllerProvider( iface );
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_GetProperty( impl->dinput_device, DIPROP_VIDPID, &vid_pid.diph )))
|
|
*value = LOWORD(vid_pid.dwData);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI game_provider_get_HardwareVersionInfo( IGameControllerProvider *iface, GameControllerVersionInfo *value )
|
|
{
|
|
FIXME( "iface %p, value %p stub!\n", iface, value );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI game_provider_get_IsConnected( IGameControllerProvider *iface, boolean *value )
|
|
{
|
|
FIXME( "iface %p, value %p stub!\n", iface, value );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static const struct IGameControllerProviderVtbl game_provider_vtbl =
|
|
{
|
|
game_provider_QueryInterface,
|
|
game_provider_AddRef,
|
|
game_provider_Release,
|
|
/* IInspectable methods */
|
|
game_provider_GetIids,
|
|
game_provider_GetRuntimeClassName,
|
|
game_provider_GetTrustLevel,
|
|
/* IGameControllerProvider methods */
|
|
game_provider_get_FirmwareVersionInfo,
|
|
game_provider_get_HardwareProductId,
|
|
game_provider_get_HardwareVendorId,
|
|
game_provider_get_HardwareVersionInfo,
|
|
game_provider_get_IsConnected,
|
|
};
|
|
|
|
static void check_haptics_caps( struct provider *provider, HANDLE device, PHIDP_PREPARSED_DATA preparsed,
|
|
HIDP_LINK_COLLECTION_NODE *collections, HIDP_VALUE_CAPS *caps )
|
|
{
|
|
USHORT count, report_len = provider->caps.FeatureReportByteLength;
|
|
ULONG parent = caps->LinkCollection, waveform = 0;
|
|
char *report_buf = provider->report_buf;
|
|
HIDP_VALUE_CAPS value_caps;
|
|
USAGE_AND_PAGE phy_usages;
|
|
NTSTATUS status;
|
|
|
|
while (collections[parent].LinkUsagePage != HID_USAGE_PAGE_HAPTICS ||
|
|
collections[parent].LinkUsage != HID_USAGE_HAPTICS_SIMPLE_CONTROLLER)
|
|
if (!(parent = collections[parent].Parent)) break;
|
|
|
|
if (collections[parent].LinkUsagePage != HID_USAGE_PAGE_HAPTICS ||
|
|
collections[parent].LinkUsage != HID_USAGE_HAPTICS_SIMPLE_CONTROLLER)
|
|
{
|
|
WARN( "Failed to find haptics simple controller collection\n" );
|
|
return;
|
|
}
|
|
phy_usages.UsagePage = collections[collections[parent].Parent].LinkUsagePage;
|
|
phy_usages.Usage = collections[collections[parent].Parent].LinkUsage;
|
|
|
|
status = HidP_InitializeReportForID( HidP_Feature, caps->ReportID, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_InitializeReportForID returned %#lx\n", status );
|
|
if (!HidD_GetFeature( device, report_buf, report_len ))
|
|
{
|
|
WARN( "Failed to get waveform list report, error %lu\n", GetLastError() );
|
|
return;
|
|
}
|
|
|
|
status = HidP_GetUsageValue( HidP_Feature, caps->UsagePage, caps->LinkCollection,
|
|
caps->NotRange.Usage, &waveform, preparsed, report_buf, report_len );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsageValue returned %#lx\n", status );
|
|
|
|
count = 1;
|
|
status = HidP_GetSpecificValueCaps( HidP_Output, HID_USAGE_PAGE_HAPTICS, parent,
|
|
HID_USAGE_HAPTICS_INTENSITY, &value_caps, &count, preparsed );
|
|
if (status != HIDP_STATUS_SUCCESS || !count) WARN( "Failed to get waveform intensity caps, status %#lx\n", status );
|
|
else if (phy_usages.UsagePage == HID_USAGE_PAGE_GENERIC && phy_usages.Usage == HID_USAGE_GENERIC_Z)
|
|
{
|
|
TRACE( "Found left rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection );
|
|
provider->haptics_report = value_caps.ReportID;
|
|
provider->haptics_left_caps = value_caps;
|
|
}
|
|
else if (phy_usages.UsagePage == HID_USAGE_PAGE_GENERIC && phy_usages.Usage == HID_USAGE_GENERIC_RZ)
|
|
{
|
|
TRACE( "Found right rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection );
|
|
provider->haptics_report = value_caps.ReportID;
|
|
provider->haptics_right_caps = value_caps;
|
|
}
|
|
else if (waveform == HID_USAGE_HAPTICS_WAVEFORM_RUMBLE)
|
|
{
|
|
TRACE( "Found rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection );
|
|
provider->haptics_report = value_caps.ReportID;
|
|
provider->haptics_rumble_caps = value_caps;
|
|
}
|
|
else if (waveform == HID_USAGE_HAPTICS_WAVEFORM_BUZZ)
|
|
{
|
|
TRACE( "Found buzz caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection );
|
|
provider->haptics_report = value_caps.ReportID;
|
|
provider->haptics_buzz_caps = value_caps;
|
|
}
|
|
else FIXME( "Unsupported waveform type %#lx\n", waveform );
|
|
}
|
|
|
|
static void open_haptics_device( struct provider *provider )
|
|
{
|
|
HIDP_LINK_COLLECTION_NODE *collections;
|
|
PHIDP_PREPARSED_DATA preparsed = NULL;
|
|
ULONG i, size, coll_count = 0;
|
|
USHORT count, caps_count = 0;
|
|
HIDP_VALUE_CAPS caps[8];
|
|
NTSTATUS status;
|
|
HANDLE device;
|
|
|
|
device = CreateFileW( provider->device_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 );
|
|
if (device == INVALID_HANDLE_VALUE) return;
|
|
|
|
if (!HidD_GetPreparsedData( device, &preparsed )) goto failed;
|
|
if (HidP_GetCaps( preparsed, &provider->caps ) != HIDP_STATUS_SUCCESS) goto failed;
|
|
|
|
size = max( provider->caps.OutputReportByteLength, provider->caps.FeatureReportByteLength );
|
|
if (!(provider->report_buf = malloc( size ))) goto failed;
|
|
|
|
coll_count = provider->caps.NumberLinkCollectionNodes;
|
|
if (!(collections = malloc( sizeof(*collections) * coll_count ))) goto failed;
|
|
|
|
status = HidP_GetLinkCollectionNodes( collections, &coll_count, preparsed );
|
|
if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetLinkCollectionNodes returned %#lx\n", status );
|
|
else for (i = 0; i < coll_count; ++i)
|
|
{
|
|
if (collections[i].LinkUsagePage != HID_USAGE_PAGE_HAPTICS) continue;
|
|
if (collections[i].LinkUsage == HID_USAGE_HAPTICS_WAVEFORM_LIST)
|
|
{
|
|
count = ARRAY_SIZE(caps) - caps_count;
|
|
status = HidP_GetSpecificValueCaps( HidP_Feature, HID_USAGE_PAGE_ORDINAL, i, 0,
|
|
caps + caps_count, &count, preparsed );
|
|
if (status == HIDP_STATUS_SUCCESS) caps_count += count;
|
|
}
|
|
}
|
|
for (i = 0; i < caps_count; ++i) check_haptics_caps( provider, device, preparsed, collections, caps + i );
|
|
free( collections );
|
|
|
|
provider->preparsed = preparsed;
|
|
provider->device = device;
|
|
return;
|
|
|
|
failed:
|
|
free( provider->report_buf );
|
|
provider->report_buf = NULL;
|
|
HidD_FreePreparsedData( preparsed );
|
|
CloseHandle( device );
|
|
}
|
|
|
|
void provider_create( const WCHAR *device_path )
|
|
{
|
|
IDirectInputDevice8W *dinput_device;
|
|
IGameControllerProvider *provider;
|
|
struct provider *impl, *entry;
|
|
GUID guid = device_path_guid;
|
|
IDirectInput8W *dinput;
|
|
BOOL found = FALSE;
|
|
const WCHAR *tmp;
|
|
HRESULT hr;
|
|
|
|
if (wcsnicmp( device_path, L"\\\\?\\HID#", 8 )) return;
|
|
if ((tmp = wcschr( device_path + 8, '#' )) && !wcsnicmp( tmp - 6, L"&IG_", 4 )) return;
|
|
|
|
TRACE( "device_path %s\n", debugstr_w( device_path ) );
|
|
|
|
*(const WCHAR **)&guid = device_path;
|
|
if (FAILED(DirectInput8Create( windows_gaming_input, DIRECTINPUT_VERSION, &IID_IDirectInput8W,
|
|
(void **)&dinput, NULL ))) return;
|
|
hr = IDirectInput8_CreateDevice( dinput, &guid, &dinput_device, NULL );
|
|
IDirectInput8_Release( dinput );
|
|
if (FAILED(hr)) return;
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_SetCooperativeLevel( dinput_device, 0, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE ))) goto done;
|
|
if (FAILED(hr = IDirectInputDevice8_SetDataFormat( dinput_device, &c_dfDIJoystick2 ))) goto done;
|
|
if (FAILED(hr = IDirectInputDevice8_Acquire( dinput_device ))) goto done;
|
|
|
|
if (!(impl = calloc( 1, sizeof(*impl) ))) goto done;
|
|
impl->IWineGameControllerProvider_iface.lpVtbl = &wine_provider_vtbl;
|
|
impl->IGameControllerProvider_iface.lpVtbl = &game_provider_vtbl;
|
|
IDirectInputDevice_AddRef( dinput_device );
|
|
impl->dinput_device = dinput_device;
|
|
impl->ref = 1;
|
|
|
|
wcscpy( impl->device_path, device_path );
|
|
list_init( &impl->entry );
|
|
open_haptics_device( impl );
|
|
|
|
provider = &impl->IGameControllerProvider_iface;
|
|
TRACE( "created WineGameControllerProvider %p\n", provider );
|
|
|
|
EnterCriticalSection( &provider_cs );
|
|
LIST_FOR_EACH_ENTRY( entry, &provider_list, struct provider, entry )
|
|
if ((found = !wcsicmp( entry->device_path, device_path ))) break;
|
|
if (!found) list_add_tail( &provider_list, &impl->entry );
|
|
LeaveCriticalSection( &provider_cs );
|
|
|
|
if (found) IGameControllerProvider_Release( provider );
|
|
else manager_on_provider_created( provider );
|
|
done:
|
|
IDirectInputDevice_Release( dinput_device );
|
|
}
|
|
|
|
void provider_remove( const WCHAR *device_path )
|
|
{
|
|
IGameControllerProvider *provider;
|
|
struct provider *entry;
|
|
BOOL found = FALSE;
|
|
|
|
TRACE( "device_path %s\n", debugstr_w( device_path ) );
|
|
|
|
EnterCriticalSection( &provider_cs );
|
|
LIST_FOR_EACH_ENTRY( entry, &provider_list, struct provider, entry )
|
|
if ((found = !wcsicmp( entry->device_path, device_path ))) break;
|
|
if (found) list_remove( &entry->entry );
|
|
LeaveCriticalSection( &provider_cs );
|
|
|
|
if (!found) WARN( "provider not found for device %s\n", debugstr_w( device_path ) );
|
|
else
|
|
{
|
|
provider = &entry->IGameControllerProvider_iface;
|
|
manager_on_provider_removed( provider );
|
|
IGameControllerProvider_Release( provider );
|
|
}
|
|
}
|