//=========== Copyright Valve Corporation, All rights reserved. ===============// // // Purpose: Native Steam Controller Interface //=============================================================================// #include "inputsystem.h" #include "key_translation.h" #include "filesystem.h" #include "steam/isteamcontroller.h" #include "math.h" #ifndef UINT64_MAX const uint64 UINT64_MAX = 0xffffffffffffffff; #endif #if !defined( NO_STEAM ) #include "steam/steam_api.h" #endif //NO_STEAM // memdbgon must be the last include file in a .cpp file!!! #include ConVar sc_joystick_map( "sc_joystick_map", "1", FCVAR_ARCHIVE, "How to map the analog joystick deadzone and extents 0 = Scaled Cross, 1 = Concentric Mapping to Square." ); #define STEAMPAD_MAX_ANALOGSAMPLE_GYRO 32768 #define STEAMPAD_MAX_ANALOGSAMPLE_TRIGGER 32768 #define STEAMPAD_MAX_ANALOGSAMPLE_LEFT 32768 #define STEAMPAD_MAX_ANALOGSAMPLE_RIGHT 32767 #define STEAMPAD_MAX_ANALOGSAMPLE_DOWN 32768 #define STEAMPAD_MAX_ANALOGSAMPLE_UP 32767 #define STEAMPAD_MAX_ANALOGSAMPLE_MAX 32768 #define STEAMPAD_ANALOG_SCALE_LEFT(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_LEFT/( float )( STEAMPAD_MAX_ANALOGSAMPLE_LEFT-(x) ) ) #define STEAMPAD_ANALOG_SCALE_RIGHT(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_RIGHT/( float )( STEAMPAD_MAX_ANALOGSAMPLE_RIGHT-(x) ) ) #define STEAMPAD_ANALOG_SCALE_DOWN(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_DOWN/( float )( STEAMPAD_MAX_ANALOGSAMPLE_DOWN-(x) ) ) #define STEAMPAD_ANALOG_SCALE_UP(x) ( ( float )STEAMPAD_MAX_ANALOGSAMPLE_UP/( float )( STEAMPAD_MAX_ANALOGSAMPLE_UP-(x) ) ) #define STEAMPAD_ANALOG_TRIGGER_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_TRIGGER * 0.5f ) ) #define STEAMPAD_ANALOG_PAD_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.25f ) ) #define STEAMPAD_ANALOG_GYRO_THRESHOLD ( ( int ) ( STEAMPAD_MAX_ANALOGSAMPLE_GYRO * 0.3f ) ) #define STEAMPAD_DIGITAL_PAD_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.52f ) ) #define STEAMPAD_AXIS_REPEAT_INTERVAL_START 0.6f #define STEAMPAD_AXIS_REPEAT_INTERVAL_END 0.05f #define STEAMPAD_AXIS_REPEAT_CURVE_TIME 1.75f #define PAD_ANALOG_BUTTON_THRESHOLD ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.285f ) ) #define PAD_ANALOG_BUTTON_THRESHOLD_STRONG ( ( int )( STEAMPAD_MAX_ANALOGSAMPLE_MAX * 0.68f ) ) #define SQRT2 1.414213562 // key translation typedef struct { uint64 steampadinput; ButtonCode_t steampadkey; } steampadInputTosteampadKey_t; static const int s_nSteamPadDeadZoneTable[] = { STEAMPAD_ANALOG_PAD_THRESHOLD, // LEFTPAD_AXIS_X STEAMPAD_ANALOG_PAD_THRESHOLD, // LEFTPAD_AXIS_Y STEAMPAD_ANALOG_PAD_THRESHOLD, // RIGHTPAD_AXIS_X STEAMPAD_ANALOG_PAD_THRESHOLD, // RIGHTPAD_AXIS_Y STEAMPAD_ANALOG_TRIGGER_THRESHOLD, //LEFT_TRIGGER_AXIS STEAMPAD_ANALOG_TRIGGER_THRESHOLD, //RIGHT_TRIGGER_AXIS STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_PITCH STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_ROLL STEAMPAD_ANALOG_GYRO_THRESHOLD, //GYRO_AXIS_YAW }; //----------------------------------------------------------------------------- // Purpose: Counts the number of active gamepads connected //----------------------------------------------------------------------------- uint32 CInputSystem::GetNumSteamControllersConnected() { return m_unNumConnected; } struct SDigitalMenuAction { const char *strName; ButtonCode_t buttonCode; ControllerDigitalActionHandle_t handle; bool bState[STEAM_CONTROLLER_MAX_COUNT]; bool bAwaitingDebounce[STEAM_CONTROLLER_MAX_COUNT]; }; static SDigitalMenuAction g_DigitalMenuActions[] = { { "menu_left", STEAMCONTROLLER_DPAD_LEFT, 0, { false }, { false } }, { "menu_right", STEAMCONTROLLER_DPAD_RIGHT, 0, { false }, { false } }, { "menu_up", STEAMCONTROLLER_DPAD_UP, 0, { false }, { false } }, { "menu_down", STEAMCONTROLLER_DPAD_DOWN, 0, { false }, { false } }, { "menu_cancel", STEAMCONTROLLER_B, 0, { false }, { false } }, { "menu_select", STEAMCONTROLLER_A, 0, { false }, { false } }, { "resume_esc", STEAMCONTROLLER_START, 0, { false }, { false } }, { "cl_trigger_first_notification", STEAMCONTROLLER_F1, 0, { false }, { false } }, // Command is in the in-game action set (use for hitting accept/ok on dialogs) { "cl_decline_first_notification", STEAMCONTROLLER_F2, 0, { false }, { false } }, // Command is in the in-game action set (use for hitting cancel/decline on dialogs) { "menu_toggle_function", STEAMCONTROLLER_Y, 0, { false }, { false }, }, // Command is in the in-game HUD action set { "menu_alt_function", STEAMCONTROLLER_X, 0, { false }, { false } }, // Command is in the in-game HUD action set { "cancelselect", STEAMCONTROLLER_START, 0, { false }, { false } }, // Command is in the in-game action set { "toggleready", STEAMCONTROLLER_F4, 0, { false }, { false } }, // Command is in the in-game action set }; struct SAnalogAction { const char *strName; JoystickAxis_t joystickAxisX, joystickAxisY; ControllerAnalogActionHandle_t handle; }; struct SGameActionSet { const char *strName; GameActionSet_t eGameActionSet; ControllerActionSetHandle_t handle; }; static SGameActionSet g_GameActionSets[] = { { "MenuControls", GAME_ACTION_SET_MENUCONTROLS, 0 }, { "FPSControls", GAME_ACTION_SET_FPSCONTROLS, 0 }, { "InGameHUDControls", GAME_ACTION_SET_IN_GAME_HUD, 0 }, { "SpectatorControls", GAME_ACTION_SET_SPECTATOR, 0 }, }; // Table that maps a physical steam controller origin to the corresponding character in our icon font. // If the EControllerActionOrigin enum or the font changes, this table must be changed to match. static const wchar_t* g_MapSteamControllerOriginToIconFont[] = { L"", // k_EControllerActionOrigin_None L"A", // k_EControllerActionOrigin_A L"B", // k_EControllerActionOrigin_B L"X", // k_EControllerActionOrigin_X L"Y", // k_EControllerActionOrigin_Y L"2", // k_EControllerActionOrigin_LeftBumper L"3", // k_EControllerActionOrigin_RightBumper L"(", // k_EControllerActionOrigin_LeftGrip L")", // k_EControllerActionOrigin_RightGrip L"5", // k_EControllerActionOrigin_Start L"4", // k_EControllerActionOrigin_Back L"q", // k_EControllerActionOrigin_LeftPad_Touch L"w", // k_EControllerActionOrigin_LeftPad_Swipe L"e", // k_EControllerActionOrigin_LeftPad_Click L"a", // k_EControllerActionOrigin_LeftPad_DPadNorth L"s", // k_EControllerActionOrigin_LeftPad_DPadSouth L"d", // k_EControllerActionOrigin_LeftPad_DPadWest L"f", // k_EControllerActionOrigin_LeftPad_DPadEast L"y", // k_EControllerActionOrigin_RightPad_Touch L"u", // k_EControllerActionOrigin_RightPad_Swipe L"i", // k_EControllerActionOrigin_RightPad_Click L"h", // k_EControllerActionOrigin_RightPad_DPadNorth L"j", // k_EControllerActionOrigin_RightPad_DPadSouth L"k", // k_EControllerActionOrigin_RightPad_DPadWest L"l", // k_EControllerActionOrigin_RightPad_DEast L"z", // k_EControllerActionOrigin_LeftTrigger_Pull L"x", // k_EControllerActionOrigin_LeftTrigger_Click L"n", // k_EControllerActionOrigin_RightTrigger_Pull L"m", // k_EControllerActionOrigin_RightTrigger_Click L"C", // k_EControllerActionOrigin_LeftStick_Move L"V", // k_EControllerActionOrigin_LeftStick_Click L"7", // k_EControllerActionOrigin_LeftStick_DPadNorth L"8", // k_EControllerActionOrigin_LeftStick_DPadSouth L"9", // k_EControllerActionOrigin_LeftStick_DPadWest L"0", // k_EControllerActionOrigin_LeftStick_DPadEast L"6", // k_EControllerActionOrigin_Gyro_Move L"6", // k_EControllerActionOrigin_Gyro_Pitch L"6", // k_EControllerActionOrigin_Gyro_Yaw L"6", // k_EControllerActionOrigin_Gyro_Roll }; // Table that maps a physical steam controller origin to a short description string for user display (only use this // if it's impractical to use the icon font). // If the EControllerActionOrigin enum changes, this table must be changed to match. static const wchar_t* g_MapSteamControllerOriginToDescription[] = { L"", // k_EControllerActionOrigin_None L"A", // k_EControllerActionOrigin_A L"B", // k_EControllerActionOrigin_B L"X", // k_EControllerActionOrigin_X L"Y", // k_EControllerActionOrigin_Y L"LB", // k_EControllerActionOrigin_LeftBumper L"RB", // k_EControllerActionOrigin_RightBumper L"LG", // k_EControllerActionOrigin_LeftGrip L"RG", // k_EControllerActionOrigin_RightGrip L"START", // k_EControllerActionOrigin_Start L"BACK", // k_EControllerActionOrigin_Back L"LPTOUCH", // k_EControllerActionOrigin_LeftPad_Touch L"LPSWIPE", // k_EControllerActionOrigin_LeftPad_Swipe L"LPCLICK", // k_EControllerActionOrigin_LeftPad_Click L"LPUP", // k_EControllerActionOrigin_LeftPad_DPadNorth L"LPDOWN", // k_EControllerActionOrigin_LeftPad_DPadSouth L"LPLEFT", // k_EControllerActionOrigin_LeftPad_DPadWest L"LPRIGHT", // k_EControllerActionOrigin_LeftPad_DPadEast L"RPTOUCH", // k_EControllerActionOrigin_RightPad_Touch L"RPSWIPE", // k_EControllerActionOrigin_RightPad_Swipe L"RPCLICK", // k_EControllerActionOrigin_RightPad_Click L"RPUP", // k_EControllerActionOrigin_RightPad_DPadNorth L"RPDOWN", // k_EControllerActionOrigin_RightPad_DPadSouth L"RPLEFT", // k_EControllerActionOrigin_RightPad_DPadWest L"RPRIGHT", // k_EControllerActionOrigin_RightPad_DEast L"LT", // k_EControllerActionOrigin_LeftTrigger_Pull L"LT", // k_EControllerActionOrigin_LeftTrigger_Click L"LT", // k_EControllerActionOrigin_RightTrigger_Pull L"LT", // k_EControllerActionOrigin_RightTrigger_Click L"LS", // k_EControllerActionOrigin_LeftStick_Move L"LSCLICK", // k_EControllerActionOrigin_LeftStick_Click L"LSUP", // k_EControllerActionOrigin_LeftStick_DPadNorth L"LSDOWN", // k_EControllerActionOrigin_LeftStick_DPadSouth L"LSLEFT", // k_EControllerActionOrigin_LeftStick_DPadWest L"LSRIGHT", // k_EControllerActionOrigin_LeftStick_DPadEast L"GYRO", // k_EControllerActionOrigin_Gyro_Move L"GYRO", // k_EControllerActionOrigin_Gyro_Pitch L"GYRO", // k_EControllerActionOrigin_Gyro_Yaw L"GYRO", // k_EControllerActionOrigin_Gyro_Roll }; bool CInputSystem::InitializeSteamControllerActionSets() { auto psteamcontroller = g_pInputSystem->SteamControllerInterface(); if ( !psteamcontroller ) { return false; } bool bGotHandles = true; for ( int i = 0; i != ARRAYSIZE( g_DigitalMenuActions ); ++i ) { g_DigitalMenuActions[i].handle = psteamcontroller->GetDigitalActionHandle( g_DigitalMenuActions[i].strName ); bGotHandles = bGotHandles && ( g_DigitalMenuActions[i].handle != 0 ); // Init the button state array for( int j = 0; j < STEAM_CONTROLLER_MAX_COUNT; j++ ) { g_DigitalMenuActions[i].bState[j] = false; } } for ( int i = 0; i != ARRAYSIZE( g_GameActionSets ); ++i ) { g_GameActionSets[i].handle = psteamcontroller->GetActionSetHandle( g_GameActionSets[i].strName ); bGotHandles = bGotHandles && g_GameActionSets[i].handle; } return bGotHandles; } ConVar sc_debug_sets( "sc_debug_sets", "0", FCVAR_ARCHIVE, "Debugging" ); void CInputSystem::PollSteamControllers( void ) { // Make sure we've got the appropriate connections to Steam auto steamcontroller = SteamControllerInterface(); if ( !steamcontroller ) { return; } steamcontroller->RunFrame(); uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; m_unNumConnected = steamcontroller->GetConnectedControllers( nControllerHandles ); if ( m_unNumConnected > 0 ) { if ( !m_bSteamControllerActionsInitialized ) { // Retry initialization of controller actions if we didn't acquire them all before for some reason. m_bSteamControllerActionsInitialized = m_bSteamController && InitializeSteamControllerActionSets(); g_pInputSystem->ActivateSteamControllerActionSet( GAME_ACTION_SET_MENUCONTROLS ); } if ( m_bSteamControllerActionsInitialized ) { // If we successfully initialized all the actions, and we have a connected controller, then we're considered "active". m_bSteamControllerActive = true; // For each digital action for ( int i = 0; i != ARRAYSIZE( g_DigitalMenuActions ); ++i ) { SDigitalMenuAction& action = g_DigitalMenuActions[i]; // and for each controller for ( uint64 j = 0; j < m_unNumConnected; ++j ) { // Get the action's current state ControllerDigitalActionData_t data = steamcontroller->GetDigitalActionData( nControllerHandles[j], action.handle ); // We only care if the action is active if ( data.bActive ) { action.bAwaitingDebounce[j] = action.bAwaitingDebounce[j] && data.bState; // If the action's state has changed if ( data.bState != action.bState[j] ) { action.bState[j] = data.bState; // Press the key for the correct controller ButtonCode_t buttonCode = ButtonCodeToJoystickButtonCode( action.buttonCode, j ); if ( action.bState[j] ) { if ( !action.bAwaitingDebounce[j] ) { PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); } } else { PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); } } } } } } } else { // If no controllers are connected, unflag the active state. m_bSteamControllerActive = false; } } bool CInputSystem::GetRadialMenuStickValues( int nSlot, float &fX, float &fY ) { fX = m_pRadialMenuStickVal[nSlot][0]; fY = m_pRadialMenuStickVal[nSlot][1]; return true; } bool CInputSystem::IsSteamControllerActive( void ) { return m_bSteamControllerActive; } bool CInputSystem::InitializeSteamControllers() { m_flLastSteamControllerInput = -FLT_MAX; auto steamcontroller = SteamControllerInterface(); if ( steamcontroller ) { if ( !steamcontroller->Init() ) { return false; } } else { return false; } for( int i=0; iGetConnectedControllers( nControllerHandles ); steamcontroller->RunFrame(); if ( m_unNumConnected > 0 ) { for ( uint32 i = 0; i < m_unNumConnected; i++ ) { if ( m_Device[i].m_nJoystickIndex == INVALID_USER_ID ) { int nJoystickIndex = i; m_Device[i].m_nJoystickIndex = nJoystickIndex; m_Device[i].m_nHardwareIndex = i; } m_nControllerType[m_Device[i].m_nJoystickIndex] = INPUT_TYPE_STEAMCONTROLLER; } } return true; } return false; } ControllerActionSetHandle_t CInputSystem::GetActionSetHandle( GameActionSet_t eActionSet ) { return g_GameActionSets[eActionSet].handle; } ControllerActionSetHandle_t CInputSystem::GetActionSetHandle( const char* szActionSet ) { for ( int i = 0; i != ARRAYSIZE( g_GameActionSets ); ++i ) { if ( !Q_strcmp( szActionSet, g_GameActionSets[i].strName ) ) { return g_GameActionSets[i].handle; } } return 0; } void CInputSystem::ActivateSteamControllerActionSetForSlot( uint64 nSlot, GameActionSet_t eActionSet ) { auto steamcontroller = SteamControllerInterface(); bool bChangedActionSet = false; if ( steamcontroller ) { if ( nSlot == STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS ) { for ( int i = 0; i < STEAM_CONTROLLER_MAX_COUNT; i++ ) { if ( m_currentActionSet[i] != eActionSet ) { bChangedActionSet = true; m_currentActionSet[i] = eActionSet; } } steamcontroller->ActivateActionSet( STEAM_CONTROLLER_HANDLE_ALL_CONTROLLERS, g_GameActionSets[eActionSet].handle ); } else { uint64 nControllerHandles[STEAM_CONTROLLER_MAX_COUNT]; int unNumConnected = steamcontroller->GetConnectedControllers( nControllerHandles ); if( nSlot < unNumConnected ) { if ( m_currentActionSet[nSlot] != eActionSet ) { bChangedActionSet = true; m_currentActionSet[nSlot] = eActionSet; } steamcontroller->ActivateActionSet( nControllerHandles[nSlot], g_GameActionSets[eActionSet].handle ); } } } if ( bChangedActionSet ) { // If we changed action set, then flag everything for a debounce (meaning we demand to see an unpressed state before we'll register a pressed one) for ( int i = 0; i < STEAM_CONTROLLER_MAX_COUNT; i++ ) { for ( int j = 0; j != ARRAYSIZE( g_DigitalMenuActions ); ++j ) { g_DigitalMenuActions[j].bAwaitingDebounce[i] = true; } } } } const int CInputSystem::GetSteamPadDeadZone( ESteamPadAxis axis ) { int nDeadzone = s_nSteamPadDeadZoneTable[ axis ]; // Do modifications if required here? return nDeadzone; } //----------------------------------------------------------------------------- // Purpose: Processes data for controller //----------------------------------------------------------------------------- void CInputSystem::ReadSteamController( int iIndex ) { } //----------------------------------------------------------------------------- // Purpose: Pulse haptic feedback //----------------------------------------------------------------------------- /* void CInputSystem::PulseHapticOnSteamController( uint32 nControllerIndex, ESteamControllerPad ePad, unsigned short durationMicroSec ) { auto steamcontroller = SteamControllerInterface(); if ( steamcontroller ) { steamcontroller->TriggerHapticPulse( nControllerIndex, ePad, durationMicroSec ); } }*/ //----------------------------------------------------------------------------- // Purpose: Returns the controller State for a particular joystick slot //----------------------------------------------------------------------------- bool CInputSystem::GetControllerStateForSlot( int nSlot ) { return false; } int CInputSystem::GetSteamControllerIndexForSlot( int nSlot ) { for ( int i = 0; i < Q_ARRAYSIZE( m_Device ); i++ ) { if ( m_Device[i].active && (int)m_Device[i].m_nJoystickIndex == nSlot ) { return m_Device[i].m_nHardwareIndex; } } return -1; } //----------------------------------------------------------------------------- // Purpose: Post events, ignoring key repeats //----------------------------------------------------------------------------- void CInputSystem::PostKeyEvent( int iIndex, sKey_t sKey, int nSample ) { // Rework of xbox code here : AnalogCode_t code = ANALOG_CODE_LAST; float value = 0.f; //int nMsgSlot = iIndex; int nMsgSlot = m_Device[iIndex].m_nJoystickIndex; int nSampleThreshold = 1; // Look for changes on the analog axes switch( sKey ) { case SK_BUTTON_LPAD_LEFT: case SK_BUTTON_LPAD_RIGHT: { code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_X ); value = ( sKey == SK_BUTTON_LPAD_LEFT ) ? -nSample : nSample; // Kind of a hack to horizontal values for menu selection items in Portal 2 // The additional 5k helps accidental horizontals in menus. nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ) + 5000; } break; case SK_BUTTON_LPAD_UP: case SK_BUTTON_LPAD_DOWN: { code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_Y ); value = ( sKey == SK_BUTTON_LPAD_UP ) ? -nSample : nSample; nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ); } break; case SK_BUTTON_RPAD_LEFT: case SK_BUTTON_RPAD_RIGHT: { code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_U ); value = ( sKey == SK_BUTTON_RPAD_LEFT ) ? -nSample : nSample; // Kind of a hack to horizontal values for menu selection items in Portal 2 // The additional 5k helps accidental horizontals in menus. nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ) + 5000;; } break; case SK_BUTTON_RPAD_UP: case SK_BUTTON_RPAD_DOWN: { code = (AnalogCode_t)JOYSTICK_AXIS( nMsgSlot, JOY_AXIS_R ); value = ( sKey == SK_BUTTON_RPAD_UP ) ? -nSample : nSample; nSampleThreshold = ( int )( STEAMPAD_DIGITAL_PAD_THRESHOLD ); } break; } // Store the analog event if ( ANALOG_CODE_LAST != code ) { InputState_t &state = m_InputState[ m_bIsPolling ]; state.m_pAnalogDelta[ code ] = ( int )( value - state.m_pAnalogValue[ code ] ); state.m_pAnalogValue[ code ] = ( int )value; if ( state.m_pAnalogDelta[ code ] != 0 ) { PostEvent( IE_AnalogValueChanged, m_nLastSampleTick, code, ( int )value, state.m_pAnalogDelta[ code ] ); } } // store the key m_Device[iIndex].m_appSKeys[sKey].sample = nSample; if ( nSample > nSampleThreshold ) { m_Device[iIndex].m_appSKeys[sKey].repeats++; } else { m_Device[iIndex].m_appSKeys[sKey].repeats = 0; nSample = 0; } if ( m_Device[iIndex].m_appSKeys[sKey].repeats > 1 ) { // application cannot handle streaming keys // first keypress is the only edge trigger return; } // package the key ButtonCode_t buttonCode = SKeyToButtonCode( nMsgSlot, sKey ); if ( nSample ) { PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); } else { PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); } } // Gets the action origin (i.e. which physical input) maps to the given virtual button for the given action set EControllerActionOrigin CInputSystem::GetSteamControllerActionOrigin( const char* action, GameActionSet_t action_set ) { auto pSC = SteamControllerInterface(); if ( pSC ) { ControllerHandle_t hConnected[STEAM_CONTROLLER_MAX_COUNT]; auto nConnected = pSC->GetConnectedControllers( hConnected ); if ( nConnected == 0 ) { return k_EControllerActionOrigin_None; } SGameActionSet* pActionSet = nullptr; for ( int i = 0; i < ARRAYSIZE( g_GameActionSets ); i++ ) { if ( g_GameActionSets[i].eGameActionSet == action_set ) { pActionSet = &g_GameActionSets[i]; break; } } if ( pActionSet ) { auto actionHandle = pSC->GetDigitalActionHandle( action ); EControllerActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int nOrigins = pSC->GetDigitalActionOrigins( hConnected[0], pActionSet->handle, actionHandle, origins ); if ( nOrigins > 0 ) { return origins[0]; } } } return k_EControllerActionOrigin_None; } // Gets the action origin (i.e. which physical input) maps to the given virtual button for the given action set EControllerActionOrigin CInputSystem::GetSteamControllerActionOrigin( const char* action, ControllerActionSetHandle_t action_set_handle ) { auto pSC = SteamControllerInterface(); if ( pSC && action_set_handle ) { ControllerHandle_t hConnected[STEAM_CONTROLLER_MAX_COUNT]; auto nConnected = pSC->GetConnectedControllers( hConnected ); if ( nConnected == 0 ) { return k_EControllerActionOrigin_None; } auto actionHandle = pSC->GetDigitalActionHandle( action ); EControllerActionOrigin origins[STEAM_CONTROLLER_MAX_ORIGINS]; int nOrigins = pSC->GetDigitalActionOrigins( hConnected[0], action_set_handle, actionHandle, origins ); if ( nOrigins > 0 ) { return origins[0]; } } return k_EControllerActionOrigin_None; } // Maps a Steam Controller action origin to a string (consisting of a single character) in our SC icon font const wchar_t* CInputSystem::GetSteamControllerFontCharacterForActionOrigin( EControllerActionOrigin origin ) { if ( origin >= 0 && origin < ARRAYSIZE( g_MapSteamControllerOriginToIconFont ) ) { return g_MapSteamControllerOriginToIconFont[origin]; } else { return L""; } } // Maps a Steam Controller action origin to a short text string (e.g. "X", "LB", "LDOWN") describing the control. // Prefer to actually use the icon font wherever possible. const wchar_t* CInputSystem::GetSteamControllerDescriptionForActionOrigin( EControllerActionOrigin origin ) { if ( origin >= 0 && origin < ARRAYSIZE( g_MapSteamControllerOriginToDescription ) ) { return g_MapSteamControllerOriginToDescription[origin]; } else { return L""; } }