diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /inputsystem/joystick_sdl.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'inputsystem/joystick_sdl.cpp')
| -rw-r--r-- | inputsystem/joystick_sdl.cpp | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/inputsystem/joystick_sdl.cpp b/inputsystem/joystick_sdl.cpp new file mode 100644 index 0000000..aef5c48 --- /dev/null +++ b/inputsystem/joystick_sdl.cpp @@ -0,0 +1,615 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Linux Joystick implementation for inputsystem.dll +// +//===========================================================================// + +/* For force feedback testing. */ +#include "inputsystem.h" +#include "tier1/convar.h" +#include "tier0/icommandline.h" + +#include "SDL.h" +#include "SDL_gamecontroller.h" +#include "SDL_haptic.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +static ButtonCode_t ControllerButtonToButtonCode( SDL_GameControllerButton button ); +static AnalogCode_t ControllerAxisToAnalogCode( SDL_GameControllerAxis axis ); +static int JoystickSDLWatcher( void *userInfo, SDL_Event *event ); + +ConVar joy_axisbutton_threshold( "joy_axisbutton_threshold", "0.3", FCVAR_ARCHIVE, "Analog axis range before a button press is registered." ); +ConVar joy_axis_deadzone( "joy_axis_deadzone", "0.2", FCVAR_ARCHIVE, "Dead zone near the zero point to not report movement." ); + +static void joy_active_changed_f( IConVar *var, const char *pOldValue, float flOldValue ); +ConVar joy_active( "joy_active", "-1", FCVAR_NONE, "Which of the connected joysticks / gamepads to use (-1 means first found)", &joy_active_changed_f); + +static void joy_gamecontroller_config_changed_f( IConVar *var, const char *pOldValue, float flOldValue ); +ConVar joy_gamecontroller_config( "joy_gamecontroller_config", "", FCVAR_ARCHIVE, "Game controller mapping (passed to SDL with SDL_HINT_GAMECONTROLLERCONFIG), can also be configured in Steam Big Picture mode.", &joy_gamecontroller_config_changed_f ); + +void SearchForDevice() +{ + int newJoystickId = joy_active.GetInt(); + CInputSystem *pInputSystem = (CInputSystem *)g_pInputSystem; + + if ( !pInputSystem ) + { + return; + } + // -1 means "first available." + if ( newJoystickId < 0 ) + { + pInputSystem->JoystickHotplugAdded(0); + return; + } + + for ( int device_index = 0; device_index < SDL_NumJoysticks(); ++device_index ) + { + SDL_Joystick *joystick = SDL_JoystickOpen(device_index); + if ( joystick == NULL ) + { + continue; + } + + int joystickId = SDL_JoystickInstanceID(joystick); + SDL_JoystickClose(joystick); + + if ( joystickId == newJoystickId ) + { + pInputSystem->JoystickHotplugAdded(device_index); + break; + } + } +} + +//--------------------------------------------------------------------------------------- +// Switch our active joystick to another device +//--------------------------------------------------------------------------------------- +void joy_active_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + SearchForDevice(); +} + +//--------------------------------------------------------------------------------------- +// Reinitialize the game controller layer when the joy_gamecontroller_config is updated. +//--------------------------------------------------------------------------------------- +void joy_gamecontroller_config_changed_f( IConVar *var, const char *pOldValue, float flOldValue ) +{ + CInputSystem *pInputSystem = (CInputSystem *)g_pInputSystem; + if ( pInputSystem && SDL_WasInit(SDL_INIT_GAMECONTROLLER) ) + { + bool oldValuePresent = pOldValue && ( strlen( pOldValue ) > 0 ); + bool newValuePresent = ( strlen( joy_gamecontroller_config.GetString() ) > 0 ); + if ( !oldValuePresent && !newValuePresent ) + { + return; + } + + // We need to reinitialize the whole thing (i.e. undo CInputSystem::InitializeJoysticks and then call it again) + // due to SDL_GameController only reading the SDL_HINT_GAMECONTROLLERCONFIG on init. + pInputSystem->ShutdownJoysticks(); + pInputSystem->InitializeJoysticks(); + } +} + +//----------------------------------------------------------------------------- +// Handle the events coming from the GameController SDL subsystem. +//----------------------------------------------------------------------------- +int JoystickSDLWatcher( void *userInfo, SDL_Event *event ) +{ + CInputSystem *pInputSystem = (CInputSystem *)userInfo; + Assert(pInputSystem != NULL); + Assert(event != NULL); + + if ( event == NULL || pInputSystem == NULL ) + { + Warning("No input system\n"); + return 1; + } + + switch ( event->type ) + { + case SDL_CONTROLLERAXISMOTION: + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + break; + default: + return 1; + } + + // This is executed on the same thread as SDL_PollEvent, as PollEvent + // updates the joystick subsystem, which then calls SDL_PushEvent for + // the various events below. PushEvent invokes this callback. + // SDL_PollEvent is called in PumpWindowsMessageLoop which is coming + // from PollInputState_Linux, so there's no worry about calling + // PostEvent (which doesn't seem to be thread safe) from other threads. + Assert(ThreadInMainThread()); + + switch ( event->type ) + { + case SDL_CONTROLLERAXISMOTION: + { + pInputSystem->JoystickAxisMotion(event->caxis.which, event->caxis.axis, event->caxis.value); + break; + } + + case SDL_CONTROLLERBUTTONDOWN: + pInputSystem->JoystickButtonPress(event->cbutton.which, event->cbutton.button); + break; + case SDL_CONTROLLERBUTTONUP: + pInputSystem->JoystickButtonRelease(event->cbutton.which, event->cbutton.button); + break; + + case SDL_CONTROLLERDEVICEADDED: + pInputSystem->JoystickHotplugAdded(event->cdevice.which); + break; + case SDL_CONTROLLERDEVICEREMOVED: + pInputSystem->JoystickHotplugRemoved(event->cdevice.which); + SearchForDevice(); + break; + } + + return 1; +} + +//----------------------------------------------------------------------------- +// Initialize all joysticks +//----------------------------------------------------------------------------- +void CInputSystem::InitializeJoysticks( void ) +{ + if ( m_bJoystickInitialized ) + { + ShutdownJoysticks(); + } + + // assume no joystick + m_nJoystickCount = 0; + memset( m_pJoystickInfo, 0, sizeof( m_pJoystickInfo ) ); + for ( int i = 0; i < MAX_JOYSTICKS; ++i ) + { + m_pJoystickInfo[ i ].m_nDeviceId = -1; + } + + // abort startup if user requests no joystick + if ( CommandLine()->FindParm("-nojoy") ) return; + + const char *controllerConfig = joy_gamecontroller_config.GetString(); + if ( strlen(controllerConfig) > 0 ) + { + DevMsg("Passing joy_gamecontroller_config to SDL ('%s').\n", controllerConfig); + // We need to pass this hint to SDL *before* we init the gamecontroller subsystem, otherwise it gets ignored. + SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG, controllerConfig); + } + + if ( SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC ) == -1 ) + { + Warning("Joystick init failed -- SDL_Init(SDL_INIT_GAMECONTROLLER|SDL_INIT_HAPTIC) failed: %s.\n", SDL_GetError()); + return; + } + + m_bJoystickInitialized = true; + + SDL_AddEventWatch(JoystickSDLWatcher, this); + + const int totalSticks = SDL_NumJoysticks(); + for ( int i = 0; i < totalSticks; i++ ) + { + if ( SDL_IsGameController(i) ) + { + JoystickHotplugAdded(i); + } + else + { + SDL_JoystickGUID joyGUID = SDL_JoystickGetDeviceGUID(i); + char szGUID[sizeof(joyGUID.data)*2 + 1]; + SDL_JoystickGetGUIDString(joyGUID, szGUID, sizeof(szGUID)); + + Msg("Found joystick '%s' (%s), but no recognized controller configuration for it.\n", SDL_JoystickNameForIndex(i), szGUID); + } + } + + if ( totalSticks < 1 ) + { + Msg("Did not detect any valid joysticks.\n"); + } +} + +void CInputSystem::ShutdownJoysticks() +{ + if ( !m_bJoystickInitialized ) + { + return; + } + + SDL_DelEventWatch( JoystickSDLWatcher, this ); + if ( m_pJoystickInfo[ 0 ].m_pDevice != NULL ) + { + JoystickHotplugRemoved( m_pJoystickInfo[ 0 ].m_nDeviceId ); + } + SDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC ); + + m_bJoystickInitialized = false; +} + +// Update the joy_xcontroller_found convar to force CInput::JoyStickMove to re-exec 360controller-linux.cfg +static void SetJoyXControllerFound( bool found ) +{ + static ConVarRef xcontrollerVar( "joy_xcontroller_found" ); + static ConVarRef joystickVar( "joystick" ); + if ( xcontrollerVar.IsValid() ) + { + xcontrollerVar.SetValue(found); + } + + if ( found && joystickVar.IsValid() ) + { + joystickVar.SetValue(true); + } +} + +void CInputSystem::JoystickHotplugAdded( int joystickIndex ) +{ + // SDL_IsGameController doesn't bounds check its inputs. + if ( joystickIndex < 0 || joystickIndex >= SDL_NumJoysticks() ) + { + return; + } + + if ( !SDL_IsGameController(joystickIndex) ) + { + Warning("Joystick is not recognized by the game controller system. You can configure the controller in Steam Big Picture mode.\n"); + return; + } + + SDL_Joystick *joystick = SDL_JoystickOpen(joystickIndex); + if ( joystick == NULL ) + { + Warning("Could not open joystick %i: %s", joystickIndex, SDL_GetError()); + return; + } + + int joystickId = SDL_JoystickInstanceID(joystick); + SDL_JoystickClose(joystick); + + int activeJoystick = joy_active.GetInt(); + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( activeJoystick < 0 ) + { + // Only opportunistically open devices if we don't have one open already. + if ( info.m_nDeviceId != -1 ) + { + Msg("Detected supported joystick #%i '%s'. Currently active joystick is #%i.\n", joystickId, SDL_JoystickNameForIndex(joystickIndex), info.m_nDeviceId); + return; + } + } + else if ( activeJoystick != joystickId ) + { + Msg("Detected supported joystick #%i '%s'. Currently active joystick is #%i.\n", joystickId, SDL_JoystickNameForIndex(joystickIndex), activeJoystick); + return; + } + + if ( info.m_nDeviceId != -1 ) + { + // Don't try to open the device we already have open. + if ( info.m_nDeviceId == joystickId ) + { + return; + } + + DevMsg("Joystick #%i already initialized, removing it first.\n", info.m_nDeviceId); + JoystickHotplugRemoved(info.m_nDeviceId); + } + + Msg("Initializing joystick #%i and making it active.\n", joystickId); + + SDL_GameController *controller = SDL_GameControllerOpen(joystickIndex); + if ( controller == NULL ) + { + Warning("Failed to open joystick %i: %s\n", joystickId, SDL_GetError()); + return; + } + + // XXX: This will fail if this is a *real* hotplug event (and not coming from the initial InitializeJoysticks call). + // That's because the SDL haptic subsystem currently doesn't do hotplugging. Everything but haptics will work fine. + SDL_Haptic *haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(controller)); + if ( haptic == NULL || SDL_HapticRumbleInit(haptic) != 0 ) + { + Warning("Unable to initialize rumble for joystick #%i: %s\n", joystickId, SDL_GetError()); + haptic = NULL; + } + + info.m_pDevice = controller; + info.m_pHaptic = haptic; + info.m_nDeviceId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller)); + info.m_nButtonCount = SDL_CONTROLLER_BUTTON_MAX; + info.m_bRumbleEnabled = false; + + SetJoyXControllerFound(true); + EnableJoystickInput(0, true); + m_nJoystickCount = 1; + m_bXController = true; + + // We reset joy_active to -1 because joystick ids are never reused - until you restart. + // Setting it to -1 means that you get expected hotplugging behavior if you disconnect the current joystick. + joy_active.SetValue(-1); +} + +void CInputSystem::JoystickHotplugRemoved( int joystickId ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + DevMsg("Ignoring hotplug remove for #%i, active joystick is #%i.\n", joystickId, info.m_nDeviceId); + return; + } + + if ( info.m_pDevice == NULL ) + { + info.m_nDeviceId = -1; + DevMsg("Got hotplug remove event for removed joystick #%i, ignoring.\n", joystickId); + return; + } + + m_nJoystickCount = 0; + m_bXController = false; + EnableJoystickInput(0, false); + SetJoyXControllerFound(false); + + SDL_HapticClose((SDL_Haptic *)info.m_pHaptic); + SDL_GameControllerClose((SDL_GameController *)info.m_pDevice); + + info.m_pHaptic = NULL; + info.m_pDevice = NULL; + info.m_nButtonCount = 0; + info.m_nDeviceId = -1; + info.m_bRumbleEnabled = false; + + Msg("Joystick %i removed.\n", joystickId); +} + +void CInputSystem::JoystickButtonPress( int joystickId, int button ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + Warning("Not active device input system (%i x %i)\n", info.m_nDeviceId, joystickId); + return; + } + + ButtonCode_t buttonCode = ControllerButtonToButtonCode((SDL_GameControllerButton)button); + PostButtonPressedEvent(IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode); +} + +void CInputSystem::JoystickButtonRelease( int joystickId, int button ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + return; + } + + ButtonCode_t buttonCode = ControllerButtonToButtonCode((SDL_GameControllerButton)button); + PostButtonReleasedEvent(IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode); +} + + +void CInputSystem::JoystickAxisMotion( int joystickId, int axis, int value ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId != joystickId ) + { + return; + } + + AnalogCode_t code = ControllerAxisToAnalogCode((SDL_GameControllerAxis)axis); + if ( code == ANALOG_CODE_INVALID ) + { + Warning("Invalid code for axis %i\n", axis); + return; + } + + ButtonCode_t buttonCode = BUTTON_CODE_NONE; + switch ( axis ) + { + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + buttonCode = KEY_XBUTTON_RTRIGGER; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + buttonCode = KEY_XBUTTON_LTRIGGER; + break; + } + + if ( buttonCode != BUTTON_CODE_NONE ) + { + int pressThreshold = joy_axisbutton_threshold.GetFloat() * 32767; + int keyIndex = buttonCode - KEY_XBUTTON_LTRIGGER; + Assert( keyIndex < ARRAYSIZE( m_appXKeys[0] ) && keyIndex >= 0 ); + + appKey_t &key = m_appXKeys[0][keyIndex]; + if ( value > pressThreshold ) + { + if ( key.repeats < 1 ) + { + PostButtonPressedEvent( IE_ButtonPressed, m_nLastSampleTick, buttonCode, buttonCode ); + } + key.repeats++; + } + else + { + PostButtonReleasedEvent( IE_ButtonReleased, m_nLastSampleTick, buttonCode, buttonCode ); + key.repeats = 0; + } + } + + int minValue = joy_axis_deadzone.GetFloat() * 32767; + if ( abs(value) < minValue ) + { + value = 0; + } + + InputState_t& state = m_InputState[ m_bIsPolling ]; + state.m_pAnalogDelta[ code ] = value - state.m_pAnalogValue[ code ]; + state.m_pAnalogValue[ code ] = value; + if ( state.m_pAnalogDelta[ code ] != 0 ) + { + PostEvent(IE_AnalogValueChanged, m_nLastSampleTick, code, value, 0); + } +} + +//----------------------------------------------------------------------------- +// Process the event +//----------------------------------------------------------------------------- +void CInputSystem::JoystickButtonEvent( ButtonCode_t button, int sample ) +{ + // Not used - we post button events from JoystickButtonPress/Release. +} + + +//----------------------------------------------------------------------------- +// Update the joystick button state +//----------------------------------------------------------------------------- +void CInputSystem::UpdateJoystickButtonState( int nJoystick ) +{ + // We don't sample - we get events posted by SDL_GameController in JoystickSDLWatcher +} + + +//----------------------------------------------------------------------------- +// Update the joystick POV control +//----------------------------------------------------------------------------- +void CInputSystem::UpdateJoystickPOVControl( int nJoystick ) +{ + // SDL GameController does not support joystick POV. Should we poll? +} + + +//----------------------------------------------------------------------------- +// Purpose: Sample the joystick +//----------------------------------------------------------------------------- +void CInputSystem::PollJoystick( void ) +{ + // We only pump the SDL event loop if we're not an SDL app, since otherwise PollInputState_Platform calls into CSDLMgr to pump it. + // Our state updates happen in events posted by SDL_GameController in JoystickSDLWatcher, so the loop is empty. +#if !defined( USE_SDL ) + SDL_Event event; + int nEventsProcessed = 0; + + SDL_PumpEvents(); + while ( SDL_PollEvent( &event ) && nEventsProcessed < 100 ) + { + nEventsProcessed++; + } +#endif +} + +void CInputSystem::SetXDeviceRumble( float fLeftMotor, float fRightMotor, int userId ) +{ + JoystickInfo_t& info = m_pJoystickInfo[ 0 ]; + if ( info.m_nDeviceId < 0 || info.m_pHaptic == NULL ) + { + return; + } + + float strength = (fLeftMotor + fRightMotor) / 2.f; + static ConVarRef joystickVar( "joystick" ); + + // 0f means "stop". + bool shouldStop = ( strength < 0.01f ); + // If they've disabled the gamecontroller in settings, never rumble. + if ( !joystickVar.IsValid() || !joystickVar.GetBool() ) + { + shouldStop = true; + } + + if ( shouldStop ) + { + if ( info.m_bRumbleEnabled ) + { + SDL_HapticRumbleStop( (SDL_Haptic *)info.m_pHaptic ); + info.m_bRumbleEnabled = false; + info.m_fCurrentRumble = 0.0f; + } + + return; + } + + // If there's little change, then don't change the rumble strength. + if ( info.m_bRumbleEnabled && abs(info.m_fCurrentRumble - strength) < 0.01f ) + { + return; + } + + info.m_bRumbleEnabled = true; + info.m_fCurrentRumble = strength; + + if ( SDL_HapticRumblePlay((SDL_Haptic *)info.m_pHaptic, strength, SDL_HAPTIC_INFINITY) != 0 ) + { + Warning("Couldn't play rumble (strength %.1f): %s\n", strength, SDL_GetError()); + } +} + +ButtonCode_t ControllerButtonToButtonCode( SDL_GameControllerButton button ) +{ + switch ( button ) + { + case SDL_CONTROLLER_BUTTON_A: // KEY_XBUTTON_A + case SDL_CONTROLLER_BUTTON_B: // KEY_XBUTTON_B + case SDL_CONTROLLER_BUTTON_X: // KEY_XBUTTON_X + case SDL_CONTROLLER_BUTTON_Y: // KEY_XBUTTON_Y + return JOYSTICK_BUTTON(0, button); + + case SDL_CONTROLLER_BUTTON_BACK: + return KEY_XBUTTON_BACK; + case SDL_CONTROLLER_BUTTON_START: + return KEY_XBUTTON_START; + + case SDL_CONTROLLER_BUTTON_GUIDE: + return KEY_XBUTTON_BACK; // XXX: How are we supposed to handle this? Steam overlay etc. + + case SDL_CONTROLLER_BUTTON_LEFTSTICK: + return KEY_XBUTTON_STICK1; + case SDL_CONTROLLER_BUTTON_RIGHTSTICK: + return KEY_XBUTTON_STICK2; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: + return KEY_XBUTTON_LEFT_SHOULDER; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: + return KEY_XBUTTON_RIGHT_SHOULDER; + + case SDL_CONTROLLER_BUTTON_DPAD_UP: + return KEY_XBUTTON_UP; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: + return KEY_XBUTTON_DOWN; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: + return KEY_XBUTTON_LEFT; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: + return KEY_XBUTTON_RIGHT; + } + + return BUTTON_CODE_NONE; +} + +AnalogCode_t ControllerAxisToAnalogCode( SDL_GameControllerAxis axis ) +{ + switch ( axis ) + { + case SDL_CONTROLLER_AXIS_LEFTX: + return JOYSTICK_AXIS(0, JOY_AXIS_X); + case SDL_CONTROLLER_AXIS_LEFTY: + return JOYSTICK_AXIS(0, JOY_AXIS_Y); + + case SDL_CONTROLLER_AXIS_RIGHTX: + return JOYSTICK_AXIS(0, JOY_AXIS_U); + case SDL_CONTROLLER_AXIS_RIGHTY: + return JOYSTICK_AXIS(0, JOY_AXIS_R); + + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + return JOYSTICK_AXIS(0, JOY_AXIS_Z); + } + + return ANALOG_CODE_INVALID; +} |