mirror of
git://source.winehq.org/git/wine.git
synced 2024-09-01 17:53:38 +00:00
802 lines
28 KiB
C
802 lines
28 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 "math.h"
|
|
|
|
#include "ddk/hidsdi.h"
|
|
#include "dinput.h"
|
|
#include "hidusage.h"
|
|
#include "provider.h"
|
|
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(input);
|
|
|
|
struct effect
|
|
{
|
|
IWineForceFeedbackEffectImpl IWineForceFeedbackEffectImpl_iface;
|
|
IForceFeedbackEffect IForceFeedbackEffect_iface;
|
|
IInspectable *IInspectable_outer;
|
|
LONG ref;
|
|
|
|
CRITICAL_SECTION cs;
|
|
IDirectInputEffect *effect;
|
|
|
|
GUID type;
|
|
DWORD axes[3];
|
|
LONG directions[3];
|
|
ULONG repeat_count;
|
|
DICONSTANTFORCE constant_force;
|
|
DIRAMPFORCE ramp_force;
|
|
DICONDITION condition;
|
|
DIPERIODIC periodic;
|
|
DIENVELOPE envelope;
|
|
DIEFFECT params;
|
|
};
|
|
|
|
static inline struct effect *impl_from_IWineForceFeedbackEffectImpl( IWineForceFeedbackEffectImpl *iface )
|
|
{
|
|
return CONTAINING_RECORD( iface, struct effect, IWineForceFeedbackEffectImpl_iface );
|
|
}
|
|
|
|
static HRESULT WINAPI effect_impl_QueryInterface( IWineForceFeedbackEffectImpl *iface, REFIID iid, void **out )
|
|
{
|
|
struct effect *impl = impl_from_IWineForceFeedbackEffectImpl( 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_IWineForceFeedbackEffectImpl ))
|
|
{
|
|
IWineForceFeedbackEffectImpl_AddRef( (*out = &impl->IWineForceFeedbackEffectImpl_iface) );
|
|
return S_OK;
|
|
}
|
|
|
|
if (IsEqualGUID( iid, &IID_IForceFeedbackEffect ))
|
|
{
|
|
IInspectable_AddRef( (*out = &impl->IForceFeedbackEffect_iface) );
|
|
return S_OK;
|
|
}
|
|
|
|
FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) );
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI effect_impl_AddRef( IWineForceFeedbackEffectImpl *iface )
|
|
{
|
|
struct effect *impl = impl_from_IWineForceFeedbackEffectImpl( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p increasing refcount to %lu.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI effect_impl_Release( IWineForceFeedbackEffectImpl *iface )
|
|
{
|
|
struct effect *impl = impl_from_IWineForceFeedbackEffectImpl( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
|
|
TRACE( "iface %p decreasing refcount to %lu.\n", iface, ref );
|
|
|
|
if (!ref)
|
|
{
|
|
if (impl->effect) IDirectInputEffect_Release( impl->effect );
|
|
impl->cs.DebugInfo->Spare[0] = 0;
|
|
DeleteCriticalSection( &impl->cs );
|
|
free( impl );
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl *iface, WineForceFeedbackEffectParameters params,
|
|
WineForceFeedbackEffectEnvelope *envelope )
|
|
{
|
|
struct effect *impl = impl_from_IWineForceFeedbackEffectImpl( iface );
|
|
DWORD count = 0;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, params %p, envelope %p.\n", iface, ¶ms, envelope );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
switch (params.type)
|
|
{
|
|
case WineForceFeedbackEffectType_Constant:
|
|
impl->repeat_count = params.constant.repeat_count;
|
|
impl->constant_force.lMagnitude = round( params.constant.gain * params.constant.direction.X * 10000 );
|
|
impl->params.dwDuration = params.constant.duration.Duration / 10;
|
|
impl->params.dwStartDelay = params.constant.start_delay.Duration / 10;
|
|
if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.constant.direction.X * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.constant.direction.Y * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.constant.direction.Z * 10000 );
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Ramp:
|
|
impl->repeat_count = params.ramp.repeat_count;
|
|
impl->ramp_force.lStart = round( params.ramp.gain * params.ramp.start_vector.X * 10000 );
|
|
impl->ramp_force.lEnd = round( params.ramp.gain * params.ramp.end_vector.X * 10000 );
|
|
impl->params.dwDuration = params.ramp.duration.Duration / 10;
|
|
impl->params.dwStartDelay = params.ramp.start_delay.Duration / 10;
|
|
if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.ramp.start_vector.X * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.ramp.start_vector.Y * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.ramp.start_vector.Z * 10000 );
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Periodic_SineWave:
|
|
case WineForceFeedbackEffectType_Periodic_TriangleWave:
|
|
case WineForceFeedbackEffectType_Periodic_SquareWave:
|
|
case WineForceFeedbackEffectType_Periodic_SawtoothWaveDown:
|
|
case WineForceFeedbackEffectType_Periodic_SawtoothWaveUp:
|
|
impl->repeat_count = params.periodic.repeat_count;
|
|
impl->periodic.dwMagnitude = round( params.periodic.gain * 10000 );
|
|
impl->periodic.dwPeriod = 1000000 / params.periodic.frequency;
|
|
impl->periodic.dwPhase = round( params.periodic.phase * 36000 );
|
|
impl->periodic.lOffset = round( params.periodic.bias * 10000 );
|
|
impl->params.dwDuration = params.periodic.duration.Duration / 10;
|
|
impl->params.dwStartDelay = params.periodic.start_delay.Duration / 10;
|
|
if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.periodic.direction.X * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.periodic.direction.Y * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.periodic.direction.Z * 10000 );
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Condition_Spring:
|
|
case WineForceFeedbackEffectType_Condition_Damper:
|
|
case WineForceFeedbackEffectType_Condition_Inertia:
|
|
case WineForceFeedbackEffectType_Condition_Friction:
|
|
impl->repeat_count = 1;
|
|
impl->condition.lPositiveCoefficient = round( atan( params.condition.positive_coeff ) / M_PI_2 * 10000 );
|
|
impl->condition.lNegativeCoefficient = round( atan( params.condition.negative_coeff ) / M_PI_2 * 10000 );
|
|
impl->condition.dwPositiveSaturation = round( params.condition.max_positive_magnitude * 10000 );
|
|
impl->condition.dwNegativeSaturation = round( params.condition.max_negative_magnitude * 10000 );
|
|
impl->condition.lDeadBand = round( params.condition.deadzone * 10000 );
|
|
impl->condition.lOffset = round( params.condition.bias * 10000 );
|
|
impl->params.dwDuration = -1;
|
|
impl->params.dwStartDelay = 0;
|
|
if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( params.condition.direction.X * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( params.condition.direction.Y * 10000 );
|
|
if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( params.condition.direction.Z * 10000 );
|
|
break;
|
|
}
|
|
|
|
if (!envelope) impl->params.lpEnvelope = NULL;
|
|
else
|
|
{
|
|
impl->envelope.dwAttackTime = envelope->attack_duration.Duration / 10;
|
|
impl->envelope.dwAttackLevel = round( envelope->attack_gain * 10000 );
|
|
impl->envelope.dwFadeTime = impl->params.dwDuration - envelope->release_duration.Duration / 10;
|
|
impl->envelope.dwFadeLevel = round( envelope->release_gain * 10000 );
|
|
impl->params.lpEnvelope = &impl->envelope;
|
|
}
|
|
|
|
if (!impl->effect) hr = S_OK;
|
|
else hr = IDirectInputEffect_SetParameters( impl->effect, &impl->params, DIEP_ALLPARAMS & ~DIEP_AXES );
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static const struct IWineForceFeedbackEffectImplVtbl effect_impl_vtbl =
|
|
{
|
|
effect_impl_QueryInterface,
|
|
effect_impl_AddRef,
|
|
effect_impl_Release,
|
|
/* IWineForceFeedbackEffectImpl methods */
|
|
effect_impl_put_Parameters,
|
|
};
|
|
|
|
DEFINE_IINSPECTABLE_OUTER( effect, IForceFeedbackEffect, struct effect, IInspectable_outer )
|
|
|
|
static HRESULT WINAPI effect_get_Gain( IForceFeedbackEffect *iface, DOUBLE *value )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( iface );
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
*value = impl->params.dwGain / 10000.;
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_put_Gain( IForceFeedbackEffect *iface, DOUBLE value )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( iface );
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %f.\n", iface, value );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
impl->params.dwGain = round( value * 10000 );
|
|
if (!impl->effect) hr = S_FALSE;
|
|
else hr = IDirectInputEffect_SetParameters( impl->effect, &impl->params, DIEP_GAIN );
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_get_State( IForceFeedbackEffect *iface, ForceFeedbackEffectState *value )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( iface );
|
|
DWORD status;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
if (!impl->effect)
|
|
*value = ForceFeedbackEffectState_Stopped;
|
|
else if (FAILED(hr = IDirectInputEffect_GetEffectStatus( impl->effect, &status )))
|
|
*value = ForceFeedbackEffectState_Faulted;
|
|
else
|
|
{
|
|
if (status == DIEGES_PLAYING) *value = ForceFeedbackEffectState_Running;
|
|
else *value = ForceFeedbackEffectState_Stopped;
|
|
}
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Start( IForceFeedbackEffect *iface )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( iface );
|
|
HRESULT hr = E_UNEXPECTED;
|
|
DWORD flags = 0;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
if (impl->effect) hr = IDirectInputEffect_Start( impl->effect, impl->repeat_count, flags );
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI effect_Stop( IForceFeedbackEffect *iface )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( iface );
|
|
HRESULT hr = E_UNEXPECTED;
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
if (impl->effect) hr = IDirectInputEffect_Stop( impl->effect );
|
|
LeaveCriticalSection( &impl->cs );
|
|
|
|
return hr;
|
|
}
|
|
|
|
static const struct IForceFeedbackEffectVtbl effect_vtbl =
|
|
{
|
|
effect_QueryInterface,
|
|
effect_AddRef,
|
|
effect_Release,
|
|
/* IInspectable methods */
|
|
effect_GetIids,
|
|
effect_GetRuntimeClassName,
|
|
effect_GetTrustLevel,
|
|
/* IForceFeedbackEffect methods */
|
|
effect_get_Gain,
|
|
effect_put_Gain,
|
|
effect_get_State,
|
|
effect_Start,
|
|
effect_Stop,
|
|
};
|
|
|
|
HRESULT force_feedback_effect_create( enum WineForceFeedbackEffectType type, IInspectable *outer, IWineForceFeedbackEffectImpl **out )
|
|
{
|
|
struct effect *impl;
|
|
|
|
TRACE( "outer %p, out %p\n", outer, out );
|
|
|
|
if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY;
|
|
impl->IWineForceFeedbackEffectImpl_iface.lpVtbl = &effect_impl_vtbl;
|
|
impl->IForceFeedbackEffect_iface.lpVtbl = &effect_vtbl;
|
|
impl->IInspectable_outer = outer;
|
|
impl->ref = 1;
|
|
|
|
switch (type)
|
|
{
|
|
case WineForceFeedbackEffectType_Constant:
|
|
impl->type = GUID_ConstantForce;
|
|
impl->params.lpvTypeSpecificParams = &impl->constant_force;
|
|
impl->params.cbTypeSpecificParams = sizeof(impl->constant_force);
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Ramp:
|
|
impl->type = GUID_RampForce;
|
|
impl->params.lpvTypeSpecificParams = &impl->ramp_force;
|
|
impl->params.cbTypeSpecificParams = sizeof(impl->ramp_force);
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Periodic_SineWave:
|
|
impl->type = GUID_Sine;
|
|
goto WineForceFeedbackEffectType_Periodic;
|
|
case WineForceFeedbackEffectType_Periodic_TriangleWave:
|
|
impl->type = GUID_Triangle;
|
|
goto WineForceFeedbackEffectType_Periodic;
|
|
case WineForceFeedbackEffectType_Periodic_SquareWave:
|
|
impl->type = GUID_Square;
|
|
goto WineForceFeedbackEffectType_Periodic;
|
|
case WineForceFeedbackEffectType_Periodic_SawtoothWaveDown:
|
|
impl->type = GUID_SawtoothDown;
|
|
goto WineForceFeedbackEffectType_Periodic;
|
|
case WineForceFeedbackEffectType_Periodic_SawtoothWaveUp:
|
|
impl->type = GUID_SawtoothUp;
|
|
goto WineForceFeedbackEffectType_Periodic;
|
|
WineForceFeedbackEffectType_Periodic:
|
|
impl->params.lpvTypeSpecificParams = &impl->periodic;
|
|
impl->params.cbTypeSpecificParams = sizeof(impl->periodic);
|
|
break;
|
|
|
|
case WineForceFeedbackEffectType_Condition_Spring:
|
|
impl->type = GUID_Spring;
|
|
goto WineForceFeedbackEffectType_Condition;
|
|
case WineForceFeedbackEffectType_Condition_Damper:
|
|
impl->type = GUID_Damper;
|
|
goto WineForceFeedbackEffectType_Condition;
|
|
case WineForceFeedbackEffectType_Condition_Inertia:
|
|
impl->type = GUID_Inertia;
|
|
goto WineForceFeedbackEffectType_Condition;
|
|
case WineForceFeedbackEffectType_Condition_Friction:
|
|
impl->type = GUID_Friction;
|
|
goto WineForceFeedbackEffectType_Condition;
|
|
WineForceFeedbackEffectType_Condition:
|
|
impl->params.lpvTypeSpecificParams = &impl->condition;
|
|
impl->params.cbTypeSpecificParams = sizeof(impl->condition);
|
|
break;
|
|
}
|
|
|
|
impl->envelope.dwSize = sizeof(DIENVELOPE);
|
|
impl->params.dwSize = sizeof(DIEFFECT);
|
|
impl->params.rgdwAxes = impl->axes;
|
|
impl->params.rglDirection = impl->directions;
|
|
impl->params.dwTriggerButton = -1;
|
|
impl->params.dwGain = 10000;
|
|
impl->params.dwFlags = DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS;
|
|
impl->params.cAxes = -1;
|
|
impl->axes[0] = DIJOFS_X;
|
|
impl->axes[1] = DIJOFS_Y;
|
|
impl->axes[2] = DIJOFS_Z;
|
|
|
|
InitializeCriticalSection( &impl->cs );
|
|
impl->cs.DebugInfo->Spare[0] = (DWORD_PTR)( __FILE__ ": effect.cs" );
|
|
|
|
*out = &impl->IWineForceFeedbackEffectImpl_iface;
|
|
TRACE( "created ForceFeedbackEffect %p\n", *out );
|
|
return S_OK;
|
|
}
|
|
|
|
struct motor
|
|
{
|
|
IForceFeedbackMotor IForceFeedbackMotor_iface;
|
|
LONG ref;
|
|
|
|
IDirectInputDevice8W *device;
|
|
};
|
|
|
|
static inline struct motor *impl_from_IForceFeedbackMotor( IForceFeedbackMotor *iface )
|
|
{
|
|
return CONTAINING_RECORD( iface, struct motor, IForceFeedbackMotor_iface );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_QueryInterface( IForceFeedbackMotor *iface, REFIID iid, void **out )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( 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_IForceFeedbackMotor ))
|
|
{
|
|
IInspectable_AddRef( (*out = &impl->IForceFeedbackMotor_iface) );
|
|
return S_OK;
|
|
}
|
|
|
|
FIXME( "%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid( iid ) );
|
|
*out = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI motor_AddRef( IForceFeedbackMotor *iface )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
ULONG ref = InterlockedIncrement( &impl->ref );
|
|
TRACE( "iface %p increasing refcount to %lu.\n", iface, ref );
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI motor_Release( IForceFeedbackMotor *iface )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
ULONG ref = InterlockedDecrement( &impl->ref );
|
|
|
|
TRACE( "iface %p decreasing refcount to %lu.\n", iface, ref );
|
|
|
|
if (!ref)
|
|
{
|
|
IDirectInputDevice8_Release( impl->device );
|
|
free( impl );
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_GetIids( IForceFeedbackMotor *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 motor_GetRuntimeClassName( IForceFeedbackMotor *iface, HSTRING *class_name )
|
|
{
|
|
return WindowsCreateString( RuntimeClass_Windows_Gaming_Input_ForceFeedback_ForceFeedbackMotor,
|
|
ARRAY_SIZE(RuntimeClass_Windows_Gaming_Input_ForceFeedback_ForceFeedbackMotor),
|
|
class_name );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_GetTrustLevel( IForceFeedbackMotor *iface, TrustLevel *trust_level )
|
|
{
|
|
FIXME( "iface %p, trust_level %p stub!\n", iface, trust_level );
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_get_AreEffectsPaused( IForceFeedbackMotor *iface, BOOLEAN *value )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
DWORD state;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_GetForceFeedbackState( impl->device, &state ))) *value = FALSE;
|
|
else *value = (state & DIGFFS_PAUSED);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_get_MasterGain( IForceFeedbackMotor *iface, double *value )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
DIPROPDWORD gain =
|
|
{
|
|
.diph =
|
|
{
|
|
.dwSize = sizeof(DIPROPDWORD),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_DEVICE,
|
|
},
|
|
};
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_GetProperty( impl->device, DIPROP_FFGAIN, &gain.diph ))) *value = 1.;
|
|
else *value = gain.dwData / 10000.;
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_put_MasterGain( IForceFeedbackMotor *iface, double value )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
DIPROPDWORD gain =
|
|
{
|
|
.diph =
|
|
{
|
|
.dwSize = sizeof(DIPROPDWORD),
|
|
.dwHeaderSize = sizeof(DIPROPHEADER),
|
|
.dwHow = DIPH_DEVICE,
|
|
},
|
|
};
|
|
|
|
TRACE( "iface %p, value %f.\n", iface, value );
|
|
|
|
gain.dwData = 10000 * value;
|
|
return IDirectInputDevice8_SetProperty( impl->device, DIPROP_FFGAIN, &gain.diph );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_get_IsEnabled( IForceFeedbackMotor *iface, BOOLEAN *value )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
DWORD state;
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_GetForceFeedbackState( impl->device, &state ))) *value = FALSE;
|
|
else *value = !(state & DIGFFS_ACTUATORSOFF);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static BOOL CALLBACK check_ffb_axes( const DIDEVICEOBJECTINSTANCEW *obj, void *args )
|
|
{
|
|
ForceFeedbackEffectAxes *value = args;
|
|
|
|
if (obj->dwType & DIDFT_FFACTUATOR)
|
|
{
|
|
if (IsEqualIID( &obj->guidType, &GUID_XAxis )) *value |= ForceFeedbackEffectAxes_X;
|
|
else if (IsEqualIID( &obj->guidType, &GUID_YAxis )) *value |= ForceFeedbackEffectAxes_Y;
|
|
else if (IsEqualIID( &obj->guidType, &GUID_ZAxis )) *value |= ForceFeedbackEffectAxes_Z;
|
|
}
|
|
|
|
return DIENUM_CONTINUE;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_get_SupportedAxes( IForceFeedbackMotor *iface, enum ForceFeedbackEffectAxes *value )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
HRESULT hr;
|
|
|
|
TRACE( "iface %p, value %p.\n", iface, value );
|
|
|
|
*value = ForceFeedbackEffectAxes_None;
|
|
if (FAILED(hr = IDirectInputDevice8_EnumObjects( impl->device, check_ffb_axes, value, DIDFT_AXIS )))
|
|
*value = ForceFeedbackEffectAxes_None;
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result )
|
|
{
|
|
struct effect *effect = impl_from_IForceFeedbackEffect( (IForceFeedbackEffect *)param );
|
|
IForceFeedbackMotor *motor = (IForceFeedbackMotor *)invoker;
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( motor );
|
|
ForceFeedbackEffectAxes supported_axes = 0;
|
|
IDirectInputEffect *dinput_effect;
|
|
HRESULT hr;
|
|
|
|
EnterCriticalSection( &effect->cs );
|
|
|
|
if (FAILED(hr = IForceFeedbackMotor_get_SupportedAxes( motor, &supported_axes )))
|
|
{
|
|
WARN( "get_SupportedAxes for motor %p returned %#lx\n", motor, hr );
|
|
effect->params.cAxes = 0;
|
|
}
|
|
else if (effect->params.cAxes == -1)
|
|
{
|
|
DWORD count = 0;
|
|
|
|
/* initialize axis mapping and re-map directions that were set with the initial mapping */
|
|
if (supported_axes & ForceFeedbackEffectAxes_X)
|
|
{
|
|
effect->directions[count] = effect->directions[0];
|
|
effect->axes[count++] = DIJOFS_X;
|
|
}
|
|
if (supported_axes & ForceFeedbackEffectAxes_Y)
|
|
{
|
|
effect->directions[count] = effect->directions[1];
|
|
effect->axes[count++] = DIJOFS_Y;
|
|
}
|
|
if (supported_axes & ForceFeedbackEffectAxes_Z)
|
|
{
|
|
effect->directions[count] = effect->directions[2];
|
|
effect->axes[count++] = DIJOFS_Z;
|
|
}
|
|
|
|
effect->params.cAxes = count;
|
|
}
|
|
|
|
if (SUCCEEDED(hr = IDirectInputDevice8_CreateEffect( impl->device, &effect->type, &effect->params,
|
|
&dinput_effect, NULL )))
|
|
{
|
|
if (effect->effect) IDirectInputEffect_Release( effect->effect );
|
|
effect->effect = dinput_effect;
|
|
IDirectInputEffect_AddRef( effect->effect );
|
|
}
|
|
|
|
LeaveCriticalSection( &effect->cs );
|
|
|
|
result->vt = VT_UI4;
|
|
if (SUCCEEDED(hr)) result->ulVal = ForceFeedbackLoadEffectResult_Succeeded;
|
|
else if (hr == DIERR_DEVICEFULL) result->ulVal = ForceFeedbackLoadEffectResult_EffectStorageFull;
|
|
else result->ulVal = ForceFeedbackLoadEffectResult_EffectNotSupported;
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_LoadEffectAsync( IForceFeedbackMotor *iface, IForceFeedbackEffect *effect,
|
|
IAsyncOperation_ForceFeedbackLoadEffectResult **async_op )
|
|
{
|
|
TRACE( "iface %p, effect %p, async_op %p.\n", iface, effect, async_op );
|
|
return async_operation_effect_result_create( (IUnknown *)iface, (IUnknown *)effect, motor_load_effect_async, async_op );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_PauseAllEffects( IForceFeedbackMotor *iface )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
return IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_PAUSE );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_ResumeAllEffects( IForceFeedbackMotor *iface )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
return IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_CONTINUE );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_StopAllEffects( IForceFeedbackMotor *iface )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( iface );
|
|
|
|
TRACE( "iface %p.\n", iface );
|
|
|
|
return IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_STOPALL );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_try_disable_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( (IForceFeedbackMotor *)invoker );
|
|
HRESULT hr;
|
|
|
|
hr = IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_SETACTUATORSOFF );
|
|
result->vt = VT_BOOL;
|
|
result->boolVal = SUCCEEDED(hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_TryDisableAsync( IForceFeedbackMotor *iface, IAsyncOperation_boolean **async_op )
|
|
{
|
|
TRACE( "iface %p, async_op %p.\n", iface, async_op );
|
|
return async_operation_boolean_create( (IUnknown *)iface, NULL, motor_try_disable_async, async_op );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_try_enable_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( (IForceFeedbackMotor *)invoker );
|
|
HRESULT hr;
|
|
|
|
hr = IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_SETACTUATORSON );
|
|
result->vt = VT_BOOL;
|
|
result->boolVal = SUCCEEDED(hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_TryEnableAsync( IForceFeedbackMotor *iface, IAsyncOperation_boolean **async_op )
|
|
{
|
|
TRACE( "iface %p, async_op %p.\n", iface, async_op );
|
|
return async_operation_boolean_create( (IUnknown *)iface, NULL, motor_try_enable_async, async_op );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_try_reset_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result )
|
|
{
|
|
struct motor *impl = impl_from_IForceFeedbackMotor( (IForceFeedbackMotor *)invoker );
|
|
HRESULT hr;
|
|
|
|
hr = IDirectInputDevice8_SendForceFeedbackCommand( impl->device, DISFFC_RESET );
|
|
result->vt = VT_BOOL;
|
|
result->boolVal = SUCCEEDED(hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_TryResetAsync( IForceFeedbackMotor *iface, IAsyncOperation_boolean **async_op )
|
|
{
|
|
TRACE( "iface %p, async_op %p.\n", iface, async_op );
|
|
return async_operation_boolean_create( (IUnknown *)iface, NULL, motor_try_reset_async, async_op );
|
|
}
|
|
|
|
static HRESULT WINAPI motor_unload_effect_async( IUnknown *iface, IUnknown *param, PROPVARIANT *result )
|
|
{
|
|
struct effect *effect = impl_from_IForceFeedbackEffect( (IForceFeedbackEffect *)param );
|
|
IDirectInputEffect *dinput_effect;
|
|
HRESULT hr;
|
|
|
|
EnterCriticalSection( &effect->cs );
|
|
dinput_effect = effect->effect;
|
|
effect->effect = NULL;
|
|
LeaveCriticalSection( &effect->cs );
|
|
|
|
if (!dinput_effect) hr = S_OK;
|
|
else
|
|
{
|
|
hr = IDirectInputEffect_Unload( dinput_effect );
|
|
IDirectInputEffect_Release( dinput_effect );
|
|
}
|
|
|
|
result->vt = VT_BOOL;
|
|
result->boolVal = SUCCEEDED(hr);
|
|
return hr;
|
|
}
|
|
|
|
static HRESULT WINAPI motor_TryUnloadEffectAsync( IForceFeedbackMotor *iface, IForceFeedbackEffect *effect,
|
|
IAsyncOperation_boolean **async_op )
|
|
{
|
|
struct effect *impl = impl_from_IForceFeedbackEffect( (IForceFeedbackEffect *)effect );
|
|
HRESULT hr = S_OK;
|
|
|
|
TRACE( "iface %p, effect %p, async_op %p.\n", iface, effect, async_op );
|
|
|
|
EnterCriticalSection( &impl->cs );
|
|
if (!impl->effect) hr = E_FAIL;
|
|
LeaveCriticalSection( &impl->cs );
|
|
if (FAILED(hr)) return hr;
|
|
|
|
return async_operation_boolean_create( (IUnknown *)iface, (IUnknown *)effect, motor_unload_effect_async, async_op );
|
|
}
|
|
|
|
static const struct IForceFeedbackMotorVtbl motor_vtbl =
|
|
{
|
|
motor_QueryInterface,
|
|
motor_AddRef,
|
|
motor_Release,
|
|
/* IInspectable methods */
|
|
motor_GetIids,
|
|
motor_GetRuntimeClassName,
|
|
motor_GetTrustLevel,
|
|
/* IForceFeedbackMotor methods */
|
|
motor_get_AreEffectsPaused,
|
|
motor_get_MasterGain,
|
|
motor_put_MasterGain,
|
|
motor_get_IsEnabled,
|
|
motor_get_SupportedAxes,
|
|
motor_LoadEffectAsync,
|
|
motor_PauseAllEffects,
|
|
motor_ResumeAllEffects,
|
|
motor_StopAllEffects,
|
|
motor_TryDisableAsync,
|
|
motor_TryEnableAsync,
|
|
motor_TryResetAsync,
|
|
motor_TryUnloadEffectAsync,
|
|
};
|
|
|
|
HRESULT force_feedback_motor_create( IDirectInputDevice8W *device, IForceFeedbackMotor **out )
|
|
{
|
|
struct motor *impl;
|
|
HRESULT hr;
|
|
|
|
TRACE( "device %p, out %p\n", device, out );
|
|
|
|
if (FAILED(hr = IDirectInputDevice8_Unacquire( device ))) goto failed;
|
|
if (FAILED(hr = IDirectInputDevice8_SetCooperativeLevel( device, GetDesktopWindow(), DISCL_BACKGROUND | DISCL_EXCLUSIVE ))) goto failed;
|
|
if (FAILED(hr = IDirectInputDevice8_Acquire( device ))) goto failed;
|
|
|
|
if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY;
|
|
impl->IForceFeedbackMotor_iface.lpVtbl = &motor_vtbl;
|
|
impl->ref = 1;
|
|
|
|
IDirectInputDevice_AddRef( device );
|
|
impl->device = device;
|
|
|
|
*out = &impl->IForceFeedbackMotor_iface;
|
|
TRACE( "created ForceFeedbackMotor %p\n", *out );
|
|
return S_OK;
|
|
|
|
failed:
|
|
IDirectInputDevice8_SetCooperativeLevel( device, 0, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE );
|
|
IDirectInputDevice8_Acquire( device );
|
|
WARN( "Failed to acquire device exclusively, hr %#lx\n", hr );
|
|
return hr;
|
|
}
|