Xrasher/engine/platform/sdl/joy_sdl.c

274 lines
6 KiB
C
Raw Normal View History

/*
joy_sdl.c - SDL gamepads
Copyright (C) 2018-2025 a1batross
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <SDL.h>
#if SDL_VERSION_ATLEAST( 2, 0, 0 )
#include "common.h"
#include "keydefs.h"
#include "input.h"
#include "client.h"
#include "events.h"
static const int g_button_mapping[] =
{
#if XASH_NSWITCH // devkitPro/SDL has inverted Nintendo layout for SDL_GameController
K_B_BUTTON, K_A_BUTTON, K_Y_BUTTON, K_X_BUTTON,
#else
K_A_BUTTON, K_B_BUTTON, K_X_BUTTON, K_Y_BUTTON,
#endif
K_BACK_BUTTON, K_MODE_BUTTON, K_START_BUTTON,
K_LSTICK, K_RSTICK,
K_L1_BUTTON, K_R1_BUTTON,
K_DPAD_UP, K_DPAD_DOWN, K_DPAD_LEFT, K_DPAD_RIGHT,
K_MISC_BUTTON,
K_PADDLE1_BUTTON, K_PADDLE2_BUTTON, K_PADDLE3_BUTTON, K_PADDLE4_BUTTON,
K_TOUCHPAD,
};
// Swap axis to follow default axis binding:
// LeftX, LeftY, RightX, RightY, TriggerRight, TriggerLeft
static const engineAxis_t g_axis_mapping[] =
{
JOY_AXIS_SIDE, // SDL_CONTROLLER_AXIS_LEFTX,
JOY_AXIS_FWD, // SDL_CONTROLLER_AXIS_LEFTY,
JOY_AXIS_PITCH, // SDL_CONTROLLER_AXIS_RIGHTX,
JOY_AXIS_YAW, // SDL_CONTROLLER_AXIS_RIGHTY,
JOY_AXIS_LT, // SDL_CONTROLLER_AXIS_TRIGGERLEFT,
JOY_AXIS_RT, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
};
static SDL_JoystickID g_current_gamepad_id = -1; // used to send rumble to
static SDL_GameController *g_current_gamepad;
static SDL_GameController **g_gamepads;
static size_t g_num_gamepads;
static void SDLash_GameControllerAddMappings( const char *name )
{
fs_offset_t len = 0;
byte *p = FS_LoadFile( name, &len, false );
if( !p )
return;
if( len > 0 && len < INT32_MAX ) // function accepts int, SDL3 fixes this
{
SDL_RWops *rwops = SDL_RWFromConstMem( p, (int)len );
SDL_GameControllerAddMappingsFromRW( rwops, true );
}
Mem_Free( p );
}
static void SDLash_SetActiveGameController( SDL_JoystickID id )
{
g_current_gamepad_id = id;
g_current_gamepad = SDL_GameControllerFromInstanceID( id );
}
static void SDLash_GameControllerAdded( int device_index )
{
SDL_GameController *gc;
SDL_GameController **list;
gc = SDL_GameControllerOpen( device_index );
if( !gc )
{
Con_Printf( S_ERROR "SDL_GameControllerOpen: %s\n", SDL_GetError( ));
return;
}
list = Mem_Realloc( host.mempool, g_gamepads, sizeof( *list ) * ( g_num_gamepads + 1 ));
list[g_num_gamepads++] = gc;
g_gamepads = list;
// set as current device if none other set
if( g_current_gamepad_id < 0 )
{
SDL_Joystick *joy = SDL_GameControllerGetJoystick( gc );
if( joy )
SDLash_SetActiveGameController( SDL_JoystickInstanceID( joy ));
}
}
static void SDLash_GameControllerRemoved( SDL_JoystickID id )
{
size_t i;
if( id == g_current_gamepad_id )
{
g_current_gamepad_id = -1;
g_current_gamepad = NULL;
}
// now close the device
for( i = 0; i < g_num_gamepads; i++ )
{
SDL_GameController *gc = g_gamepads[i];
SDL_Joystick *joy;
if( !gc )
continue;
joy = SDL_GameControllerGetJoystick( gc );
if( !joy )
continue;
if( SDL_JoystickInstanceID( joy ) == id )
{
SDL_GameControllerClose( gc );
g_gamepads[i] = NULL;
}
}
}
/*
=============
SDLash_JoyInit
=============
*/
static int SDLash_JoyInit( void )
{
int count, numJoysticks, i;
Con_Reportf( "Joystick: SDL GameController API\n" );
if( SDL_WasInit( SDL_INIT_GAMECONTROLLER ) != SDL_INIT_GAMECONTROLLER &&
SDL_InitSubSystem( SDL_INIT_GAMECONTROLLER ))
{
Con_Reportf( "Failed to initialize SDL GameController API: %s\n", SDL_GetError() );
return 0;
}
SDLash_GameControllerAddMappings( "gamecontrollerdb.txt" ); // shipped in extras.pk3
SDLash_GameControllerAddMappings( "controllermappings.txt" );
count = 0;
numJoysticks = SDL_NumJoysticks();
for ( i = 0; i < numJoysticks; i++ )
{
if( SDL_IsGameController( i ))
++count;
}
return count;
}
void SDLash_HandleGameControllerEvent( SDL_Event *ev )
{
int x;
switch( ev->type )
{
case SDL_CONTROLLERAXISMOTION:
SDLash_SetActiveGameController( ev->caxis.which );
x = ev->caxis.axis;
if( x >= 0 && x < ARRAYSIZE( g_axis_mapping ))
Joy_AxisMotionEvent( g_axis_mapping[x], ev->caxis.value );
break;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
SDLash_SetActiveGameController( ev->cbutton.which );
x = ev->cbutton.button;
if( x >= 0 && x < ARRAYSIZE( g_button_mapping ))
Key_Event( g_button_mapping[x], ev->cbutton.state );
break;
case SDL_CONTROLLERDEVICEREMOVED:
SDLash_GameControllerRemoved( ev->cdevice.which );
break;
case SDL_CONTROLLERDEVICEADDED:
SDLash_GameControllerAdded( ev->cdevice.which );
break;
}
}
/*
=============
Platform_Vibrate
=============
*/
void Platform_Vibrate( float time, char flags )
{
#if SDL_VERSION_ATLEAST( 2, 0, 9 )
SDL_GameController *gc = g_current_gamepad;
if( g_current_gamepad_id < 0 || !gc )
return;
// a1ba: time is in milliseconds but might be not enough
// to spin up rumble
SDL_GameControllerRumble( gc, 0xFFFF, 0xFFFF, (int)floor( time ));
#endif // SDL_VERSION_ATLEAST( 2, 0, 9 )
}
/*
=============
Platform_JoyInit
=============
*/
int Platform_JoyInit( void )
{
return SDLash_JoyInit();
}
/*
=============
Platform_JoyShutdown
=============
*/
void Platform_JoyShutdown( void )
{
size_t i;
for( i = 0; i < g_num_gamepads; i++ )
{
if( !g_gamepads[i] )
continue;
SDL_GameControllerClose( g_gamepads[i] );
g_gamepads[i] = NULL;
}
Mem_Free( g_gamepads );
g_gamepads = NULL;
g_num_gamepads = 0;
g_current_gamepad = NULL;
g_current_gamepad_id = -1;
SDL_QuitSubSystem( SDL_INIT_GAMECONTROLLER );
}
#else // SDL_VERSION_ATLEAST( 2, 0, 0 )
void Platform_Vibrate( float time, char flags )
{
}
int Platform_JoyInit( void )
{
return 0;
}
void Platform_JoyShutdown( void )
{
}
#endif