lowpoly-walking-simulator/directx11_hellovr/DirectXTK/Src/GamePad.cpp

1208 lines
38 KiB
C++
Raw Normal View History

2024-11-14 11:54:38 +00:00
//--------------------------------------------------------------------------------------
// File: GamePad.cpp
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------
#include "pch.h"
#include "GamePad.h"
#include "PlatformHelpers.h"
using namespace DirectX;
using Microsoft::WRL::ComPtr;
namespace
{
float ApplyLinearDeadZone( float value, float maxValue, float deadZoneSize )
{
if ( value < -deadZoneSize )
{
// Increase negative values to remove the deadzone discontinuity.
value += deadZoneSize;
}
else if ( value > deadZoneSize )
{
// Decrease positive values to remove the deadzone discontinuity.
value -= deadZoneSize;
}
else
{
// Values inside the deadzone come out zero.
return 0;
}
// Scale into 0-1 range.
float scaledValue = value / (maxValue - deadZoneSize);
return std::max( -1.f, std::min( scaledValue, 1.f ) );
}
void ApplyStickDeadZone( float x, float y, GamePad::DeadZone deadZoneMode, float maxValue, float deadZoneSize,
_Out_ float& resultX, _Out_ float& resultY)
{
switch( deadZoneMode )
{
case GamePad::DEAD_ZONE_INDEPENDENT_AXES:
resultX = ApplyLinearDeadZone( x, maxValue, deadZoneSize );
resultY = ApplyLinearDeadZone( y, maxValue, deadZoneSize );
break;
case GamePad::DEAD_ZONE_CIRCULAR:
{
float dist = sqrtf( x*x + y*y );
float wanted = ApplyLinearDeadZone( dist, maxValue, deadZoneSize );
float scale = (wanted > 0.f) ? ( wanted / dist ) : 0.f;
resultX = std::max( -1.f, std::min( x * scale, 1.f ) );
resultY = std::max( -1.f, std::min( y * scale, 1.f ) );
}
break;
default: // GamePad::DEAD_ZONE_NONE
resultX = ApplyLinearDeadZone( x, maxValue, 0 );
resultY = ApplyLinearDeadZone( y, maxValue, 0 );
break;
}
}
}
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN10)
//======================================================================================
// Windows::Gaming::Input (Windows 10)
//======================================================================================
#include <Windows.Gaming.Input.h>
class GamePad::Impl
{
public:
Impl(GamePad* owner) :
mOwner(owner)
{
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
mAddedToken.value = 0;
mRemovedToken.value = 0;
if ( s_gamePad )
{
throw std::exception( "GamePad is a singleton" );
}
s_gamePad = this;
mChanged.reset( CreateEventEx( nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE ) );
if ( !mChanged )
{
throw std::exception( "CreateEventEx" );
}
HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad).Get(), mStatics.GetAddressOf() );
ThrowIfFailed( hr );
typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad AddedHandler;
hr = mStatics->add_GamepadAdded(Callback<AddedHandler>(GamepadAdded).Get(), &mAddedToken );
ThrowIfFailed( hr );
typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad RemovedHandler;
hr = mStatics->add_GamepadRemoved(Callback<RemovedHandler>(GamepadRemoved).Get(), &mRemovedToken );
ThrowIfFailed( hr );
ScanGamePads();
}
~Impl()
{
if ( mStatics )
{
mStatics->remove_GamepadAdded( mAddedToken );
mStatics->remove_GamepadRemoved( mRemovedToken );
mStatics.Reset();
}
s_gamePad = nullptr;
}
void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
{
using namespace Microsoft::WRL;
using namespace ABI::Windows::Gaming::Input;
if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
{
ScanGamePads();
}
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
GamepadReading reading;
HRESULT hr = mGamePad[ player ]->GetCurrentReading( &reading );
if ( SUCCEEDED(hr) )
{
state.connected = true;
state.packet = reading.Timestamp;
state.buttons.a = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
state.buttons.b = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
state.buttons.x = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
state.buttons.y = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;
state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;
state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;
state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;
state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;
ApplyStickDeadZone( static_cast<float>(reading.LeftThumbstickX), static_cast<float>(reading.LeftThumbstickY),
deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
static_cast<float>(state.thumbSticks.leftX), static_cast<float>(state.thumbSticks.leftY) );
ApplyStickDeadZone( static_cast<float>(reading.RightThumbstickX), static_cast<float>(reading.RightThumbstickY),
deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
static_cast<float>(state.thumbSticks.rightX), static_cast<float>(state.thumbSticks.rightY) );
state.triggers.left = static_cast<float>(reading.LeftTrigger);
state.triggers.right = static_cast<float>(reading.RightTrigger);
return;
}
}
}
memset( &state, 0, sizeof(State) );
}
void GetCapabilities( int player, _Out_ Capabilities& caps )
{
if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
{
ScanGamePads();
}
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
caps.connected = true;
caps.gamepadType = Capabilities::GAMEPAD;
caps.id = 0;
return;
}
}
memset( &caps, 0, sizeof(Capabilities) );
}
bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
{
using namespace ABI::Windows::Gaming::Input;
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
GamepadVibration vib;
vib.LeftMotor = leftMotor;
vib.RightMotor = rightMotor;
vib.LeftTrigger = leftTrigger;
vib.RightTrigger = rightTrigger;
HRESULT hr = mGamePad[ player ]->put_Vibration(vib);
if ( SUCCEEDED(hr) )
return true;
}
}
return false;
}
void Suspend()
{
for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
{
mGamePad[ j ].Reset();
}
}
void Resume()
{
// Make sure we rescan gamepads
SetEvent( mChanged.get() );
}
GamePad* mOwner;
static GamePad::Impl* s_gamePad;
private:
void ScanGamePads()
{
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Gaming::Input;
ComPtr<IVectorView<Gamepad*>> pads;
HRESULT hr = mStatics->get_Gamepads( pads.GetAddressOf() );
ThrowIfFailed( hr );
unsigned int count = 0;
hr = pads->get_Size( &count );
ThrowIfFailed( hr );
// Check for removed gamepads
for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
{
if ( mGamePad[ j ] )
{
unsigned int k = 0;
for( ; k < count; ++k )
{
ComPtr<IGamepad> pad;
hr = pads->GetAt( k, pad.GetAddressOf() );
if ( SUCCEEDED(hr) && ( pad == mGamePad[ j ] ) )
{
break;
}
}
if ( k >= count )
{
mGamePad[ j ].Reset();
}
}
}
// Check for added gamepads
for( unsigned int j = 0; j < count; ++j )
{
ComPtr<IGamepad> pad;
hr = pads->GetAt( j, pad.GetAddressOf() );
if ( SUCCEEDED(hr) )
{
size_t empty = MAX_PLAYER_COUNT;
size_t k = 0;
for( ; k < MAX_PLAYER_COUNT; ++k )
{
if ( mGamePad[ k ] == pad )
{
break;
}
else if ( !mGamePad[ k ] )
{
if ( empty >= MAX_PLAYER_COUNT )
empty = k;
}
}
if ( k >= MAX_PLAYER_COUNT )
{
// Silently ignore "extra" gamepads as there's no hard limit
if ( empty < MAX_PLAYER_COUNT )
{
mGamePad[ empty ] = pad;
}
}
}
}
}
ComPtr<ABI::Windows::Gaming::Input::IGamepadStatics> mStatics;
ComPtr<ABI::Windows::Gaming::Input::IGamepad> mGamePad[ MAX_PLAYER_COUNT ];
EventRegistrationToken mAddedToken;
EventRegistrationToken mRemovedToken;
ScopedHandle mChanged;
static HRESULT GamepadAdded( IInspectable *, ABI::Windows::Gaming::Input::IGamepad* )
{
if ( s_gamePad )
{
SetEvent( s_gamePad->mChanged.get() );
}
return S_OK;
}
static HRESULT GamepadRemoved( IInspectable *, ABI::Windows::Gaming::Input::IGamepad* )
{
if ( s_gamePad )
{
SetEvent( s_gamePad->mChanged.get() );
}
return S_OK;
}
};
GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;
#elif defined(_XBOX_ONE)
//======================================================================================
// Windows::Xbox::Input (Xbox One)
//======================================================================================
#include <Windows.Xbox.Input.h>
#ifdef _TITLE
#include <Windows.Foundation.Collections.h>
namespace
{
class GamepadAddedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadAddedEventArgs *>,
Microsoft::WRL::FtmBase>
{
public:
GamepadAddedListener(HANDLE event) : mEvent(event) {}
STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadAddedEventArgs * ) override
{
SetEvent( mEvent );
return S_OK;
}
private:
HANDLE mEvent;
};
class GamepadRemovedListener : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
ABI::Windows::Foundation::IEventHandler<ABI::Windows::Xbox::Input::GamepadRemovedEventArgs *>,
Microsoft::WRL::FtmBase>
{
public:
GamepadRemovedListener(HANDLE event) : mEvent(event) {}
STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IGamepadRemovedEventArgs * ) override
{
SetEvent( mEvent );
return S_OK;
}
private:
HANDLE mEvent;
};
}
#endif
class GamePad::Impl
{
public:
Impl(GamePad *owner) :
mOwner(owner)
{
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
mAddedToken.value = 0;
mRemovedToken.value = 0;
if ( s_gamePad )
{
throw std::exception( "GamePad is a singleton" );
}
s_gamePad = this;
mChanged.reset( CreateEventEx( nullptr, nullptr, 0, EVENT_MODIFY_STATE | SYNCHRONIZE ) );
if ( !mChanged )
{
throw std::exception( "CreateEventEx" );
}
HRESULT hr = GetActivationFactory( HStringReference(RuntimeClass_Windows_Xbox_Input_Gamepad).Get(), mStatics.GetAddressOf() );
ThrowIfFailed( hr );
#ifdef _TITLE
// This is a workaround for some registration issues in the GameOS
hr = mStatics->add_GamepadAdded(Make<GamepadAddedListener>(mChanged.get()).Get(), &mAddedToken );
ThrowIfFailed( hr );
hr = mStatics->add_GamepadRemoved(Make<GamepadRemovedListener>(mChanged.get()).Get(), &mRemovedToken );
ThrowIfFailed( hr );
#else
typedef __FIEventHandler_1_Windows__CXbox__CInput__CGamepadAddedEventArgs AddedHandler;
hr = mStatics->add_GamepadAdded(Callback<AddedHandler>(GamepadAdded).Get(), &mAddedToken );
ThrowIfFailed( hr );
typedef __FIEventHandler_1_Windows__CXbox__CInput__CGamepadRemovedEventArgs RemovedHandler;
hr = mStatics->add_GamepadRemoved(Callback<RemovedHandler>(GamepadRemoved).Get(), &mRemovedToken );
ThrowIfFailed( hr );
#endif
ScanGamePads();
}
~Impl()
{
if ( mStatics )
{
mStatics->remove_GamepadAdded( mAddedToken );
mStatics->remove_GamepadRemoved( mRemovedToken );
mStatics.Reset();
}
s_gamePad = nullptr;
}
void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
{
using namespace Microsoft::WRL;
using namespace ABI::Windows::Xbox::Input;
if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
{
ScanGamePads();
}
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
RawGamepadReading reading;
HRESULT hr = mGamePad[ player ]->GetRawCurrentReading( &reading );
if ( SUCCEEDED(hr) )
{
state.connected = true;
state.packet = reading.Timestamp;
state.buttons.a = (reading.Buttons & GamepadButtons::GamepadButtons_A) != 0;
state.buttons.b = (reading.Buttons & GamepadButtons::GamepadButtons_B) != 0;
state.buttons.x = (reading.Buttons & GamepadButtons::GamepadButtons_X) != 0;
state.buttons.y = (reading.Buttons & GamepadButtons::GamepadButtons_Y) != 0;
state.buttons.leftStick = (reading.Buttons & GamepadButtons::GamepadButtons_LeftThumbstick) != 0;
state.buttons.rightStick = (reading.Buttons & GamepadButtons::GamepadButtons_RightThumbstick) != 0;
state.buttons.leftShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_LeftShoulder) != 0;
state.buttons.rightShoulder = (reading.Buttons & GamepadButtons::GamepadButtons_RightShoulder) != 0;
state.buttons.back = (reading.Buttons & GamepadButtons::GamepadButtons_View) != 0;
state.buttons.start = (reading.Buttons & GamepadButtons::GamepadButtons_Menu) != 0;
state.dpad.up = (reading.Buttons & GamepadButtons::GamepadButtons_DPadUp) != 0;
state.dpad.down = (reading.Buttons & GamepadButtons::GamepadButtons_DPadDown) != 0;
state.dpad.right = (reading.Buttons & GamepadButtons::GamepadButtons_DPadRight) != 0;
state.dpad.left = (reading.Buttons & GamepadButtons::GamepadButtons_DPadLeft) != 0;
ApplyStickDeadZone( reading.LeftThumbstickX, reading.LeftThumbstickY,
deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
state.thumbSticks.leftX, state.thumbSticks.leftY );
ApplyStickDeadZone( reading.RightThumbstickX, reading.RightThumbstickY,
deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */,
state.thumbSticks.rightX, state.thumbSticks.rightY );
state.triggers.left = reading.LeftTrigger;
state.triggers.right = reading.RightTrigger;
return;
}
}
}
memset( &state, 0, sizeof(State) );
}
void GetCapabilities( int player, _Out_ Capabilities& caps )
{
using namespace ABI::Windows::Xbox::Input;
if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 )
{
ScanGamePads();
}
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
caps.connected = true;
caps.gamepadType = Capabilities::GAMEPAD;
ComPtr<IController> ctrl;
HRESULT hr = mGamePad[ player ].As( &ctrl );
if ( SUCCEEDED(hr) && ctrl )
{
hr = ctrl->get_Id( &caps.id );
if ( FAILED(hr) )
caps.id = 0;
}
else
caps.id = 0;
return;
}
}
memset( &caps, 0, sizeof(Capabilities) );
}
bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
{
using namespace ABI::Windows::Xbox::Input;
if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) )
{
if ( mGamePad[ player ] )
{
HRESULT hr;
try
{
GamepadVibration vib;
vib.LeftMotorLevel = leftMotor;
vib.RightMotorLevel = rightMotor;
vib.LeftTriggerLevel = leftTrigger;
vib.RightTriggerLevel = rightTrigger;
hr = mGamePad[ player ]->SetVibration(vib);
}
catch( ... )
{
// Handle case where gamepad might be invalid
hr = E_FAIL;
}
if ( SUCCEEDED(hr) )
return true;
}
}
return false;
}
void Suspend()
{
for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
{
mGamePad[ j ].Reset();
}
}
void Resume()
{
// Make sure we rescan gamepads
SetEvent( mChanged.get() );
}
GamePad* mOwner;
static GamePad::Impl* s_gamePad;
private:
void ScanGamePads()
{
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Xbox::Input;
ComPtr<IVectorView<IGamepad*>> pads;
HRESULT hr = mStatics->get_Gamepads( pads.GetAddressOf() );
ThrowIfFailed( hr );
unsigned int count = 0;
hr = pads->get_Size( &count );
ThrowIfFailed( hr );
// Check for removed gamepads
for( size_t j = 0; j < MAX_PLAYER_COUNT; ++j )
{
if ( mGamePad[ j ] )
{
unsigned int k = 0;
for( ; k < count; ++k )
{
ComPtr<IGamepad> pad;
hr = pads->GetAt( k, pad.GetAddressOf() );
if ( SUCCEEDED(hr) && ( pad == mGamePad[ j ] ) )
{
break;
}
}
if ( k >= count )
{
mGamePad[ j ].Reset();
}
}
}
// Check for added gamepads
for( unsigned int j = 0; j < count; ++j )
{
ComPtr<IGamepad> pad;
hr = pads->GetAt( j, pad.GetAddressOf() );
if ( SUCCEEDED(hr) )
{
size_t empty = MAX_PLAYER_COUNT;
size_t k = 0;
for( ; k < MAX_PLAYER_COUNT; ++k )
{
if ( mGamePad[ k ] == pad )
{
break;
}
else if ( !mGamePad[ k ] )
{
if ( empty >= MAX_PLAYER_COUNT )
empty = k;
}
}
if ( k >= MAX_PLAYER_COUNT )
{
if ( empty >= MAX_PLAYER_COUNT )
{
throw std::exception( "Too many gamepads found" );
}
mGamePad[ empty ] = pad;
}
}
}
}
ComPtr<ABI::Windows::Xbox::Input::IGamepadStatics> mStatics;
ComPtr<ABI::Windows::Xbox::Input::IGamepad> mGamePad[ MAX_PLAYER_COUNT ];
EventRegistrationToken mAddedToken;
EventRegistrationToken mRemovedToken;
ScopedHandle mChanged;
#ifndef _TITLE
static HRESULT GamepadAdded( IInspectable *, ABI::Windows::Xbox::Input::IGamepadAddedEventArgs * )
{
if ( s_gamePad )
{
SetEvent( s_gamePad->mChanged.get() );
}
return S_OK;
}
static HRESULT GamepadRemoved( IInspectable *, ABI::Windows::Xbox::Input::IGamepadRemovedEventArgs* )
{
if ( s_gamePad )
{
SetEvent( s_gamePad->mChanged.get() );
}
return S_OK;
}
#endif
};
GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;
#elif defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
//======================================================================================
// Null device for Windows Phone
//======================================================================================
class GamePad::Impl
{
public:
Impl(GamePad* owner) :
mOwner(owner)
{
if ( s_gamePad )
{
throw std::exception( "GamePad is a singleton" );
}
s_gamePad = this;
}
~Impl()
{
s_gamePad = nullptr;
}
void GetState(int player, _Out_ State& state, DeadZone)
{
UNREFERENCED_PARAMETER(player);
memset( &state, 0, sizeof(State) );
}
void GetCapabilities(int player, _Out_ Capabilities& caps)
{
UNREFERENCED_PARAMETER(player);
memset( &caps, 0, sizeof(Capabilities) );
}
bool SetVibration(int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger)
{
UNREFERENCED_PARAMETER(player);
UNREFERENCED_PARAMETER(leftMotor);
UNREFERENCED_PARAMETER(rightMotor);
UNREFERENCED_PARAMETER(leftTrigger);
UNREFERENCED_PARAMETER(rightTrigger);
return false;
}
void Suspend()
{
}
void Resume()
{
}
GamePad* mOwner;
static GamePad::Impl* s_gamePad;
};
GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;
#else
//======================================================================================
// XInput
//======================================================================================
#include <xinput.h>
static_assert( GamePad::MAX_PLAYER_COUNT == XUSER_MAX_COUNT, "xinput.h mismatch" );
class GamePad::Impl
{
public:
Impl(GamePad* owner) :
mOwner(owner)
{
for( int j = 0; j < XUSER_MAX_COUNT; ++j )
{
ClearSlot( j, 0 );
}
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
mSuspended = false;
#endif
if ( s_gamePad )
{
throw std::exception( "GamePad is a singleton" );
}
s_gamePad = this;
}
~Impl()
{
s_gamePad = nullptr;
}
void GetState( int player, _Out_ State& state, DeadZone deadZoneMode )
{
if ( !ThrottleRetry(player) )
{
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
if ( mSuspended )
{
memset( &state, 0, sizeof(State) );
state.connected = mConnected[ player ];
return;
}
#endif
XINPUT_STATE xstate;
DWORD result = XInputGetState( DWORD(player), &xstate );
if ( result == ERROR_DEVICE_NOT_CONNECTED )
{
ClearSlot( player, GetTickCount64() );
}
else
{
mConnected[ player ] = true;
state.connected = true;
state.packet = xstate.dwPacketNumber;
WORD xbuttons = xstate.Gamepad.wButtons;
state.buttons.a = (xbuttons & XINPUT_GAMEPAD_A) != 0;
state.buttons.b = (xbuttons & XINPUT_GAMEPAD_B) != 0;
state.buttons.x = (xbuttons & XINPUT_GAMEPAD_X) != 0;
state.buttons.y = (xbuttons & XINPUT_GAMEPAD_Y) != 0;
state.buttons.leftStick = (xbuttons & XINPUT_GAMEPAD_LEFT_THUMB) != 0;
state.buttons.rightStick = (xbuttons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0;
state.buttons.leftShoulder = (xbuttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
state.buttons.rightShoulder = (xbuttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
state.buttons.back = (xbuttons & XINPUT_GAMEPAD_BACK) != 0;
state.buttons.start = (xbuttons & XINPUT_GAMEPAD_START) != 0;
state.dpad.up = (xbuttons & XINPUT_GAMEPAD_DPAD_UP) != 0;
state.dpad.down = (xbuttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
state.dpad.right = (xbuttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
state.dpad.left = (xbuttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
if ( deadZoneMode == DEAD_ZONE_NONE )
{
state.triggers.left = ApplyLinearDeadZone( float(xstate.Gamepad.bLeftTrigger), 255.f, 0.f );
state.triggers.right = ApplyLinearDeadZone( float(xstate.Gamepad.bRightTrigger), 255.f, 0.f );
}
else
{
state.triggers.left = ApplyLinearDeadZone( float(xstate.Gamepad.bLeftTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD) );
state.triggers.right = ApplyLinearDeadZone( float(xstate.Gamepad.bRightTrigger), 255.f, float(XINPUT_GAMEPAD_TRIGGER_THRESHOLD) );
}
ApplyStickDeadZone( float(xstate.Gamepad.sThumbLX), float(xstate.Gamepad.sThumbLY),
deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE),
state.thumbSticks.leftX, state.thumbSticks.leftY );
ApplyStickDeadZone( float(xstate.Gamepad.sThumbRX), float(xstate.Gamepad.sThumbRY),
deadZoneMode, 32767.f, float(XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE),
state.thumbSticks.rightX, state.thumbSticks.rightY );
return;
}
}
memset( &state, 0, sizeof(State) );
}
void GetCapabilities( int player, _Out_ Capabilities& caps )
{
if ( !ThrottleRetry(player) )
{
XINPUT_CAPABILITIES xcaps;
DWORD result = XInputGetCapabilities( DWORD(player), 0, &xcaps );
if ( result == ERROR_DEVICE_NOT_CONNECTED )
{
ClearSlot( player, GetTickCount64() );
}
else
{
mConnected[ player ] = true;
caps.connected = true;
caps.id = uint64_t( player );
if ( xcaps.Type == XINPUT_DEVTYPE_GAMEPAD )
{
static_assert(Capabilities::GAMEPAD == XINPUT_DEVSUBTYPE_GAMEPAD, "xinput.h mismatch");
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
static_assert( XINPUT_DEVSUBTYPE_WHEEL == Capabilities::WHEEL, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_ARCADE_STICK == Capabilities::ARCADE_STICK, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_FLIGHT_STICK == Capabilities::FLIGHT_STICK, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_DANCE_PAD == Capabilities::DANCE_PAD, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_GUITAR == Capabilities::GUITAR, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE == Capabilities::GUITAR_ALTERNATE, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_DRUM_KIT == Capabilities::DRUM_KIT, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_GUITAR_BASS == Capabilities::GUITAR_BASS, "xinput.h mismatch");
static_assert( XINPUT_DEVSUBTYPE_ARCADE_PAD == Capabilities::ARCADE_PAD, "xinput.h mismatch");
#endif
caps.gamepadType = Capabilities::Type(xcaps.SubType);
}
return;
}
}
memset( &caps, 0, sizeof(Capabilities) );
}
bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
{
if ( ThrottleRetry(player) )
{
return false;
}
// XInput does not provide a way to set the left/right trigger impulse motors on the Xbox One Controller,
// and these motors are not present on the Xbox 360 Common Controller
UNREFERENCED_PARAMETER(leftTrigger);
UNREFERENCED_PARAMETER(rightTrigger);
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
mLeftMotor[ player ] = leftMotor;
mRightMotor[ player ] = rightMotor;
if ( mSuspended )
return mConnected[ player ];
#endif
XINPUT_VIBRATION xvibration;
xvibration.wLeftMotorSpeed = WORD( leftMotor * 0xFFFF );
xvibration.wRightMotorSpeed = WORD( rightMotor * 0xFFFF );
DWORD result = XInputSetState( DWORD(player), &xvibration );
if ( result == ERROR_DEVICE_NOT_CONNECTED )
{
ClearSlot( player, GetTickCount64() );
return false;
}
else
{
mConnected[ player ] = true;
return (result == ERROR_SUCCESS);
}
}
void Suspend()
{
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
XInputEnable( FALSE );
#else
// For XInput 9.1.0, we have to emulate the behavior of XInputEnable( FALSE )
if ( !mSuspended )
{
for( size_t j = 0; j < XUSER_MAX_COUNT; ++j )
{
if ( mConnected[ j ] )
{
XINPUT_VIBRATION xvibration;
xvibration.wLeftMotorSpeed = xvibration.wRightMotorSpeed = 0;
(void)XInputSetState( DWORD(j), &xvibration );
}
}
mSuspended = true;
}
#endif
}
void Resume()
{
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
XInputEnable( TRUE );
#else
// For XInput 9.1.0, we have to emulate the behavior of XInputEnable( TRUE )
if ( mSuspended )
{
for( int j = 0; j < XUSER_MAX_COUNT; ++j )
{
if ( mConnected[ j ] )
{
XINPUT_VIBRATION xvibration;
xvibration.wLeftMotorSpeed = WORD( mLeftMotor[ j ] * 0xFFFF );
xvibration.wRightMotorSpeed = WORD( mRightMotor[ j ] * 0xFFFF );
DWORD result = XInputSetState( DWORD(j), &xvibration );
if ( result == ERROR_DEVICE_NOT_CONNECTED )
{
ClearSlot( j, GetTickCount64() );
}
}
}
mSuspended = false;
}
#endif
}
GamePad* mOwner;
static GamePad::Impl* s_gamePad;
private:
bool mConnected[ XUSER_MAX_COUNT ];
ULONGLONG mLastReadTime[ XUSER_MAX_COUNT ];
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
// Variables for emulating XInputEnable on XInput 9.1.0
float mLeftMotor[ XUSER_MAX_COUNT ];
float mRightMotor[ XUSER_MAX_COUNT ];
bool mSuspended;
#endif
bool ThrottleRetry( int player )
{
// This function minimizes a potential performance issue with XInput on Windows when
// checking a disconnected controller slot which requires device enumeration.
// This throttling keeps checks for newly connected gamepads to about once a second
if ( ( player < 0 ) || ( player >= XUSER_MAX_COUNT ) )
return true;
if ( mConnected[ player ] )
return false;
ULONGLONG time = GetTickCount64();
for( size_t j = 0; j < XUSER_MAX_COUNT; ++j )
{
if ( !mConnected[j] )
{
LONGLONG delta = time - mLastReadTime[j];
LONGLONG interval = 1000;
if ( (int)j != player )
interval /= 4;
if ( (delta >= 0) && (delta < interval) )
return true;
}
}
return false;
}
void ClearSlot( int player, ULONGLONG time )
{
mConnected[ player ] = false;
mLastReadTime[ player ] = time;
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
mLeftMotor[ player ] = mRightMotor[ player ] = 0.f;
#endif
}
};
GamePad::Impl* GamePad::Impl::s_gamePad = nullptr;
#endif
#pragma warning( disable : 4355 )
// Public constructor.
GamePad::GamePad()
: pImpl( new Impl(this) )
{
}
// Move constructor.
GamePad::GamePad(GamePad&& moveFrom)
: pImpl(std::move(moveFrom.pImpl))
{
pImpl->mOwner = this;
}
// Move assignment.
GamePad& GamePad::operator= (GamePad&& moveFrom)
{
pImpl = std::move(moveFrom.pImpl);
pImpl->mOwner = this;
return *this;
}
// Public destructor.
GamePad::~GamePad()
{
}
GamePad::State GamePad::GetState(int player, DeadZone deadZoneMode)
{
State state;
pImpl->GetState(player, state, deadZoneMode);
return state;
}
GamePad::Capabilities GamePad::GetCapabilities(int player)
{
Capabilities caps;
pImpl->GetCapabilities(player, caps);
return caps;
}
bool GamePad::SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger )
{
return pImpl->SetVibration( player, leftMotor, rightMotor, leftTrigger, rightTrigger );
}
void GamePad::Suspend()
{
pImpl->Suspend();
}
void GamePad::Resume()
{
pImpl->Resume();
}
GamePad& GamePad::Get()
{
if ( !Impl::s_gamePad || !Impl::s_gamePad->mOwner )
throw std::exception( "GamePad is a singleton" );
return *Impl::s_gamePad->mOwner;
}
//======================================================================================
// ButtonStateTracker
//======================================================================================
#define UPDATE_BUTTON_STATE(field) field = static_cast<ButtonState>( ( !!state.buttons.field ) | ( ( !!state.buttons.field ^ !!lastState.buttons.field ) << 1 ) );
void GamePad::ButtonStateTracker::Update( const GamePad::State& state )
{
UPDATE_BUTTON_STATE(a);
assert( ( !state.buttons.a && !lastState.buttons.a ) == ( a == UP ) );
assert( ( state.buttons.a && lastState.buttons.a ) == ( a == HELD ) );
assert( ( !state.buttons.a && lastState.buttons.a ) == ( a == RELEASED ) );
assert( ( state.buttons.a && !lastState.buttons.a ) == ( a == PRESSED ) );
UPDATE_BUTTON_STATE(b);
UPDATE_BUTTON_STATE(x);
UPDATE_BUTTON_STATE(y);
UPDATE_BUTTON_STATE(leftStick);
UPDATE_BUTTON_STATE(rightStick);
UPDATE_BUTTON_STATE(leftShoulder);
UPDATE_BUTTON_STATE(rightShoulder);
UPDATE_BUTTON_STATE(back);
UPDATE_BUTTON_STATE(start);
dpadUp = static_cast<ButtonState>( ( !!state.dpad.up ) | ( ( !!state.dpad.up ^ !!lastState.dpad.up ) << 1 ) );
dpadDown = static_cast<ButtonState>( ( !!state.dpad.down ) | ( ( !!state.dpad.down ^ !!lastState.dpad.down ) << 1 ) );
dpadLeft = static_cast<ButtonState>( ( !!state.dpad.left ) | ( ( !!state.dpad.left ^ !!lastState.dpad.left ) << 1 ) );
dpadRight = static_cast<ButtonState>( ( !!state.dpad.right ) | ( ( !!state.dpad.right ^ !!lastState.dpad.right ) << 1 ) );
assert( ( !state.dpad.up && !lastState.dpad.up ) == ( dpadUp == UP ) );
assert( ( state.dpad.up && lastState.dpad.up ) == ( dpadUp == HELD ) );
assert( ( !state.dpad.up && lastState.dpad.up ) == ( dpadUp == RELEASED ) );
assert( ( state.dpad.up && !lastState.dpad.up ) == ( dpadUp == PRESSED ) );
lastState = state;
}
#undef UPDATE_BUTTON_STATE
void GamePad::ButtonStateTracker::Reset()
{
memset( this, 0, sizeof(ButtonStateTracker) );
}