//-------------------------------------------------------------------------------------- // 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 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(GamepadAdded).Get(), &mAddedToken ); ThrowIfFailed( hr ); typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad RemovedHandler; hr = mStatics->add_GamepadRemoved(Callback(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(reading.LeftThumbstickX), static_cast(reading.LeftThumbstickY), deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */, static_cast(state.thumbSticks.leftX), static_cast(state.thumbSticks.leftY) ); ApplyStickDeadZone( static_cast(reading.RightThumbstickX), static_cast(reading.RightThumbstickY), deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */, static_cast(state.thumbSticks.rightX), static_cast(state.thumbSticks.rightY) ); state.triggers.left = static_cast(reading.LeftTrigger); state.triggers.right = static_cast(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> 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 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 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 mStatics; ComPtr 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 #ifdef _TITLE #include namespace { class GamepadAddedListener : public Microsoft::WRL::RuntimeClass, ABI::Windows::Foundation::IEventHandler, 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, ABI::Windows::Foundation::IEventHandler, 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(mChanged.get()).Get(), &mAddedToken ); ThrowIfFailed( hr ); hr = mStatics->add_GamepadRemoved(Make(mChanged.get()).Get(), &mRemovedToken ); ThrowIfFailed( hr ); #else typedef __FIEventHandler_1_Windows__CXbox__CInput__CGamepadAddedEventArgs AddedHandler; hr = mStatics->add_GamepadAdded(Callback(GamepadAdded).Get(), &mAddedToken ); ThrowIfFailed( hr ); typedef __FIEventHandler_1_Windows__CXbox__CInput__CGamepadRemovedEventArgs RemovedHandler; hr = mStatics->add_GamepadRemoved(Callback(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 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> 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 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 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 mStatics; ComPtr 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 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( ( !!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( ( !!state.dpad.up ) | ( ( !!state.dpad.up ^ !!lastState.dpad.up ) << 1 ) ); dpadDown = static_cast( ( !!state.dpad.down ) | ( ( !!state.dpad.down ^ !!lastState.dpad.down ) << 1 ) ); dpadLeft = static_cast( ( !!state.dpad.left ) | ( ( !!state.dpad.left ^ !!lastState.dpad.left ) << 1 ) ); dpadRight = static_cast( ( !!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) ); }