csgo-2018-source/inputsystem/joystick_linux.cpp
2021-07-24 21:11:47 -07:00

567 lines
18 KiB
C++

//===== Copyright (c) 1996-2005, 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;
CInputSystem::JoystickInfo_t& currentJoystick = pInputSystem->m_pJoystickInfo[ 0 ];
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) )
{
// 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.
SDL_DelEventWatch(JoystickSDLWatcher, pInputSystem);
if ( pInputSystem->m_pJoystickInfo[ 0 ].m_pDevice != NULL )
{
pInputSystem->JoystickHotplugRemoved(pInputSystem->m_pJoystickInfo[ 0 ].m_nDeviceId);
}
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
pInputSystem->InitializeJoysticks();
}
}
//-----------------------------------------------------------------------------
// Handle the events coming from the GameController SDL subsystem.
//-----------------------------------------------------------------------------
int JoystickSDLWatcher( void *userInfo, SDL_Event *event )
{
// 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());
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:
{
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 )
{
// assume no joystick
m_nJoystickCount = 0;
// abort startup if user requests no joystick
if ( CommandLine()->FindParm("-nojoy") ) return;
if ( !SDL_WasInit(SDL_INIT_GAMECONTROLLER) )
{
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) == -1 )
{
Warning("Gamecontroller not found -- SDL_Init(SDL_INIT_GAMECONTROLLER) failed: %s.\n", SDL_GetError());
return;
}
SDL_AddEventWatch(JoystickSDLWatcher, this);
}
if ( !SDL_WasInit(SDL_INIT_HAPTIC) )
{
if ( SDL_InitSubSystem(SDL_INIT_HAPTIC) == -1 )
{
Warning("Joystick init failed -- SDL_Init(SDL_INIT_HAPTIC) failed: %s.\n", SDL_GetError());
return;
}
}
memset(m_pJoystickInfo, 0, sizeof(m_pJoystickInfo));
for ( int i = 0; i < MAX_JOYSTICKS; ++i )
{
m_pJoystickInfo[ i ].m_nDeviceId = -1;
}
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");
}
}
// 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");
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::AxisAnalogButtonEvent( ButtonCode_t buttonCode, bool bState, int nLastSampleTick )
{
int keyIndex = buttonCode - JOYSTICK_FIRST_AXIS_BUTTON;
Assert(keyIndex >= 0 && keyIndex < ARRAYSIZE(m_appXKeys[0]));
bool bExistingState = m_appXKeys[0][keyIndex].repeats > 0;
if ( bState != bExistingState )
{
if ( bState )
{
PostButtonPressedEvent( IE_ButtonPressed, nLastSampleTick, buttonCode, buttonCode );
}
else
{
PostButtonReleasedEvent( IE_ButtonReleased, nLastSampleTick, buttonCode, buttonCode );
}
m_appXKeys[0][keyIndex].repeats = bState ? 1 : 0;
}
}
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;
}
int minValue = joy_axis_deadzone.GetFloat() * 32767;
if ( abs(value) < minValue )
{
value = 0;
}
// Trigger right and left both map to the Z axis, but they only have positive values.
// To differentiate, right trigger is negative values, left is positive.
switch ( axis )
{
case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
AxisAnalogButtonEvent( KEY_XBUTTON_RTRIGGER, value != 0, m_nLastSampleTick );
break;
case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
AxisAnalogButtonEvent( KEY_XBUTTON_LTRIGGER, value != 0, m_nLastSampleTick );
break;
case SDL_CONTROLLER_AXIS_LEFTX:
AxisAnalogButtonEvent( KEY_XSTICK1_LEFT, value < 0, m_nLastSampleTick );
AxisAnalogButtonEvent( KEY_XSTICK1_RIGHT, value > 0, m_nLastSampleTick );
break;
case SDL_CONTROLLER_AXIS_LEFTY:
AxisAnalogButtonEvent( KEY_XSTICK1_UP, value < 0, m_nLastSampleTick );
AxisAnalogButtonEvent( KEY_XSTICK1_DOWN, value > 0, m_nLastSampleTick );
break;
}
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, state.m_pAnalogValue[ code ] , state.m_pAnalogDelta[ code ] );
}
}
//-----------------------------------------------------------------------------
// 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 don't sample - we get events posted by SDL_GameController in JoystickSDLWatcher
}
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;
if ( info.m_bRumbleEnabled && abs(info.m_fCurrentRumble - strength) < 0.01f )
{
return;
}
else
{
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;
}