//-------------------------------------------------------------------------------------- // 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) //====================================================================================== #pragma warning(push) #pragma warning(disable : 4471) #include #pragma warning(pop) class GamePad::Impl { public: Impl(GamePad* owner) : mOwner(owner), mCtrlChanged(INVALID_HANDLE_VALUE), mUserChanged(INVALID_HANDLE_VALUE), mMostRecentGamepad(0) { using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; mAddedToken.value = 0; mRemovedToken.value = 0; memset( &mUserChangeToken, 0, sizeof(mUserChangeToken) ); 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" ); } ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad).Get(), mStatics.GetAddressOf() ) ); typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad AddedHandler; ThrowIfFailed( mStatics->add_GamepadAdded(Callback(GamepadAdded).Get(), &mAddedToken ) ); typedef __FIEventHandler_1_Windows__CGaming__CInput__CGamepad RemovedHandler; ThrowIfFailed( mStatics->add_GamepadRemoved(Callback(GamepadRemoved).Get(), &mRemovedToken ) ); ScanGamePads(); } ~Impl() { using namespace ABI::Windows::Gaming::Input; for (size_t j = 0; j < MAX_PLAYER_COUNT; ++j) { if (mGamePad[j]) { ComPtr ctrl; HRESULT hr = mGamePad[j].As(&ctrl); if (SUCCEEDED(hr) && ctrl) { (void)ctrl->remove_UserChanged( mUserChangeToken[j] ); mUserChangeToken[j].value = 0; } mGamePad[j].Reset(); } } if ( mStatics ) { (void)mStatics->remove_GamepadAdded( mAddedToken ); mAddedToken.value = 0; (void)mStatics->remove_GamepadRemoved( mRemovedToken ); mRemovedToken.value = 0; 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 == -1) player = mMostRecentGamepad; 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 */, state.thumbSticks.leftX, state.thumbSticks.leftY ); ApplyStickDeadZone( static_cast(reading.RightThumbstickX), static_cast(reading.RightThumbstickY), deadZoneMode, 1.f, .24f /* Recommended Xbox One deadzone */, state.thumbSticks.rightX, 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, Capabilities& caps ) { using namespace Microsoft::WRL; using namespace ABI::Windows::System; using namespace ABI::Windows::Gaming::Input; if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 ) { ScanGamePads(); } if (player == -1) player = mMostRecentGamepad; if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) ) { if ( mGamePad[ player ] ) { caps.connected = true; caps.gamepadType = Capabilities::GAMEPAD; caps.id.clear(); ComPtr ctrl; HRESULT hr = mGamePad[player].As(&ctrl); if (SUCCEEDED(hr) && ctrl) { ComPtr user; hr = ctrl->get_User(user.GetAddressOf()); if (SUCCEEDED(hr) && user != nullptr) { Wrappers::HString str; hr = user->get_NonRoamableId(str.GetAddressOf()); if (SUCCEEDED(hr)) { caps.id = str.GetRawBuffer(nullptr); } } } return; } } caps.id.clear(); caps = {}; } bool SetVibration( int player, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger ) { using namespace ABI::Windows::Gaming::Input; if (player == -1) player = mMostRecentGamepad; 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; HANDLE mCtrlChanged; HANDLE mUserChanged; private: int mMostRecentGamepad; void ScanGamePads() { using namespace Microsoft::WRL; using namespace ABI::Windows::Foundation::Collections; using namespace ABI::Windows::Gaming::Input; ComPtr> pads; ThrowIfFailed( mStatics->get_Gamepads( pads.GetAddressOf() ) ); unsigned int count = 0; ThrowIfFailed( pads->get_Size( &count ) ); // 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; HRESULT hr = pads->GetAt( k, pad.GetAddressOf() ); if ( SUCCEEDED(hr) && ( pad == mGamePad[ j ] ) ) { break; } } if ( k >= count ) { ComPtr ctrl; HRESULT hr = mGamePad[ j ].As(&ctrl); if (SUCCEEDED(hr) && ctrl) { (void)ctrl->remove_UserChanged( mUserChangeToken[ j ] ); mUserChangeToken[j].value = 0; } mGamePad[ j ].Reset(); } } } // Check for added gamepads for( unsigned int j = 0; j < count; ++j ) { ComPtr pad; HRESULT 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 ) { if (j == (count - 1)) mMostRecentGamepad = static_cast(k); 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; if (j == (count - 1)) mMostRecentGamepad = static_cast(empty); ComPtr ctrl; hr = pad.As(&ctrl); if (SUCCEEDED(hr) && ctrl) { typedef __FITypedEventHandler_2_Windows__CGaming__CInput__CIGameController_Windows__CSystem__CUserChangedEventArgs UserHandler; ThrowIfFailed(ctrl->add_UserChanged(Callback(UserChanged).Get(), &mUserChangeToken[ empty ])); } } } } } } ComPtr mStatics; ComPtr mGamePad[ MAX_PLAYER_COUNT ]; EventRegistrationToken mUserChangeToken[ 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() ); if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE) { SetEvent( s_gamePad->mCtrlChanged ); } } return S_OK; } static HRESULT GamepadRemoved( IInspectable *, ABI::Windows::Gaming::Input::IGamepad* ) { if ( s_gamePad ) { SetEvent( s_gamePad->mChanged.get() ); if (s_gamePad->mCtrlChanged != INVALID_HANDLE_VALUE) { SetEvent( s_gamePad->mCtrlChanged ); } } return S_OK; } static HRESULT UserChanged( ABI::Windows::Gaming::Input::IGameController*, ABI::Windows::System::IUserChangedEventArgs* ) { if (s_gamePad) { if (s_gamePad->mUserChanged != INVALID_HANDLE_VALUE) { SetEvent( s_gamePad->mUserChanged ); } } return S_OK; } }; GamePad::Impl* GamePad::Impl::s_gamePad = nullptr; #elif defined(_XBOX_ONE) //====================================================================================== // Windows::Xbox::Input (Xbox One) //====================================================================================== #include #include class GamePad::Impl { public: 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); auto pad = GamePad::Impl::s_gamePad; if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE) { SetEvent(pad->mCtrlChanged); } 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); auto pad = GamePad::Impl::s_gamePad; if (pad && pad->mCtrlChanged != INVALID_HANDLE_VALUE) { SetEvent(pad->mCtrlChanged); } return S_OK; } private: HANDLE mEvent; }; class UserPairingListener : public Microsoft::WRL::RuntimeClass, ABI::Windows::Foundation::IEventHandler, Microsoft::WRL::FtmBase> { public: UserPairingListener() {} STDMETHOD(Invoke)(_In_ IInspectable *, _In_ ABI::Windows::Xbox::Input::IControllerPairingChangedEventArgs *) override { auto pad = GamePad::Impl::s_gamePad; if (pad && pad->mUserChanged != INVALID_HANDLE_VALUE) { SetEvent(pad->mUserChanged); } return S_OK; } }; Impl(GamePad *owner) : mOwner(owner), mCtrlChanged(INVALID_HANDLE_VALUE), mUserChanged(INVALID_HANDLE_VALUE), mMostRecentGamepad(0) { using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; mAddedToken.value = 0; mRemovedToken.value = 0; mUserParingToken.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" ); } ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Xbox_Input_Gamepad).Get(), mStatics.GetAddressOf() ) ); ThrowIfFailed( GetActivationFactory( HStringReference(RuntimeClass_Windows_Xbox_Input_Controller).Get(), mStaticsCtrl.GetAddressOf() ) ); ThrowIfFailed( mStatics->add_GamepadAdded(Make(mChanged.get()).Get(), &mAddedToken ) ); ThrowIfFailed( mStatics->add_GamepadRemoved(Make(mChanged.get()).Get(), &mRemovedToken ) ); ThrowIfFailed( mStaticsCtrl->add_ControllerPairingChanged(Make().Get(), &mUserParingToken ) ); ScanGamePads(); } ~Impl() { if ( mStatics ) { (void)mStatics->remove_GamepadAdded( mAddedToken ); mAddedToken.value = 0; (void)mStatics->remove_GamepadRemoved( mRemovedToken ); mRemovedToken.value = 0; mStatics.Reset(); } if (mStaticsCtrl) { (void)mStaticsCtrl->remove_ControllerPairingChanged( mUserParingToken ); mUserParingToken.value = 0; mStaticsCtrl.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 == -1) player = mMostRecentGamepad; 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 Microsoft::WRL; using namespace ABI::Windows::Xbox::Input; if ( WaitForSingleObjectEx( mChanged.get(), 0, FALSE ) == WAIT_OBJECT_0 ) { ScanGamePads(); } if (player == -1) player = mMostRecentGamepad; if ( ( player >= 0 ) && ( player < MAX_PLAYER_COUNT ) ) { if ( mGamePad[ player ] ) { caps.connected = true; caps.gamepadType = Capabilities::UNKNOWN; ComPtr ctrl; HRESULT hr = mGamePad[ player ].As( &ctrl ); if ( SUCCEEDED(hr) && ctrl ) { hr = ctrl->get_Id( &caps.id ); if ( FAILED(hr) ) caps.id = 0; Wrappers::HString str; hr = ctrl->get_Type(str.GetAddressOf()); if ( SUCCEEDED(hr) ) { const wchar_t* typeStr = str.GetRawBuffer(nullptr); if ( _wcsicmp(typeStr, L"Windows.Xbox.Input.Gamepad") == 0 ) { caps.gamepadType = Capabilities::GAMEPAD; } else if ( _wcsicmp(typeStr, L"Microsoft.Xbox.Input.ArcadeStick") == 0 ) { caps.gamepadType = Capabilities::ARCADE_STICK; } else if ( _wcsicmp(typeStr, L"Microsoft.Xbox.Input.Wheel") == 0 ) { caps.gamepadType = Capabilities::WHEEL; } } } 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 == -1) player = mMostRecentGamepad; 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; HANDLE mCtrlChanged; HANDLE mUserChanged; private: int mMostRecentGamepad; void ScanGamePads() { using namespace ABI::Windows::Foundation::Collections; using namespace ABI::Windows::Xbox::Input; ComPtr> pads; ThrowIfFailed( mStatics->get_Gamepads( pads.GetAddressOf() ) ); unsigned int count = 0; ThrowIfFailed( pads->get_Size( &count ) ); // 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; HRESULT 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; HRESULT 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 ) { if (!j) mMostRecentGamepad = static_cast(k); 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; if (!j) mMostRecentGamepad = static_cast(empty); } } } } ComPtr mStatics; ComPtr mStaticsCtrl; ComPtr mGamePad[ MAX_PLAYER_COUNT ]; EventRegistrationToken mAddedToken; EventRegistrationToken mRemovedToken; EventRegistrationToken mUserParingToken; ScopedHandle mChanged; }; 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 (player == -1) player = GetMostRecent(); ULONGLONG time = GetTickCount64(); if ( !ThrottleRetry(player, time) ) { #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, time ); } else { if (!mConnected[player]) mLastReadTime[player] = time; 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 (player == -1) player = GetMostRecent(); ULONGLONG time = GetTickCount64(); if ( !ThrottleRetry(player, time) ) { XINPUT_CAPABILITIES xcaps; DWORD result = XInputGetCapabilities( DWORD(player), 0, &xcaps ); if ( result == ERROR_DEVICE_NOT_CONNECTED ) { ClearSlot( player, time ); } else { if (!mConnected[player]) mLastReadTime[player] = time; 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 (player == -1) player = GetMostRecent(); ULONGLONG time = GetTickCount64(); if ( ThrottleRetry(player, time) ) { 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, time ); return false; } else { if (!mConnected[player]) mLastReadTime[player] = time; 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 ) { ULONGLONG time = GetTickCount64(); 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, time ); } } } 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, ULONGLONG time ) { // 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; 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 } int GetMostRecent() { int player = -1; ULONGLONG time = 0; for (size_t j = 0; j < XUSER_MAX_COUNT; ++j) { if (mConnected[j] && (mLastReadTime[j] > time)) { time = mLastReadTime[j]; player = static_cast(j); } } return player; } }; 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(); } #if (_WIN32_WINNT >= 0x0A00 /*_WIN32_WINNT_WIN10*/ ) || defined(_XBOX_ONE) void GamePad::RegisterEvents(HANDLE ctrlChanged, HANDLE userChanged) { pImpl->mCtrlChanged = (!ctrlChanged) ? INVALID_HANDLE_VALUE : ctrlChanged; pImpl->mUserChanged = (!userChanged) ? INVALID_HANDLE_VALUE : userChanged; } #endif 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 ) ); // Handle 'threshold' tests which emulate buttons bool threshold = state.IsLeftThumbStickUp(); leftStickUp = static_cast( ( !!threshold) | ( ( !!threshold ^ !!lastState.IsLeftThumbStickUp() ) << 1 ) ); threshold = state.IsLeftThumbStickDown(); leftStickDown = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickDown()) << 1)); threshold = state.IsLeftThumbStickLeft(); leftStickLeft = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickLeft()) << 1)); threshold = state.IsLeftThumbStickRight(); leftStickRight = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsLeftThumbStickRight()) << 1)); threshold = state.IsRightThumbStickUp(); rightStickUp = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickUp()) << 1)); threshold = state.IsRightThumbStickDown(); rightStickDown = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickDown()) << 1)); threshold = state.IsRightThumbStickLeft(); rightStickLeft = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickLeft()) << 1)); threshold = state.IsRightThumbStickRight(); rightStickRight = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsRightThumbStickRight()) << 1)); threshold = state.IsLeftTriggerPressed(); leftTrigger = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsLeftTriggerPressed()) << 1)); threshold = state.IsRightTriggerPressed(); rightTrigger = static_cast((!!threshold) | ((!!threshold ^ !!lastState.IsRightTriggerPressed()) << 1)); lastState = state; } #undef UPDATE_BUTTON_STATE void GamePad::ButtonStateTracker::Reset() { memset( this, 0, sizeof(ButtonStateTracker) ); }