Add gamepad device selection to menu (#2197)

* Add gamepad device selection to menu

* Prevent accidentally disabling gamepad when it's being used
This commit is contained in:
ceski 2025-02-12 21:57:58 -08:00 committed by GitHub
parent 703e9e4aaa
commit 2a4eff0d59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 233 additions and 56 deletions

View File

@ -46,6 +46,7 @@ enum
};
static boolean joy_enable;
int joy_device, last_joy_device;
joy_platform_t joy_platform;
static int joy_stick_layout;
static int joy_forward_sensitivity;
@ -489,6 +490,7 @@ static void RefreshSettings(void)
void I_BindGamepadVariables(void)
{
BIND_BOOL(joy_enable, true, "Enable gamepad");
BIND_NUM_GENERAL(joy_device, 1, 0, UL, "Gamepad device (do not modify)");
BIND_NUM(joy_platform, PLATFORM_AUTO, PLATFORM_AUTO, NUM_PLATFORMS - 1,
"Gamepad platform (0 = Auto; 1 = Xbox 360; 2 = Xbox One/Series; "
"3 = Playstation 3; 4 = Playstation 4; 5 = Playstation 5; 6 = Switch)");

View File

@ -70,6 +70,7 @@ typedef struct axes_s
float outer_deadzone; // Normalized outer deadzone.
} axes_t;
extern int joy_device, last_joy_device; // Gamepad device.
extern joy_platform_t joy_platform; // Gamepad platform (button names).
extern boolean joy_invert_forward; // Invert forward axis.
extern boolean joy_invert_strafe; // Invert strafe axis.

View File

@ -30,13 +30,17 @@
#include "i_rumble.h"
#include "i_system.h"
#include "i_timer.h"
#include "m_array.h"
#include "m_config.h"
#include "m_input.h"
#include "m_misc.h"
#include "mn_menu.h"
#define AXIS_BUTTON_DEADZONE (SDL_JOYSTICK_AXIS_MAX / 3)
static const char **gamepad_strings;
static SDL_GameController *gamepad;
static SDL_JoystickID gamepad_instance_id = -1;
static boolean gyro_supported;
static joy_platform_t platform;
@ -430,9 +434,23 @@ static void DisableGamepadEvents(void)
static void I_ShutdownGamepad(void)
{
I_ShutdownRumble();
if (gamepad)
{
SDL_GameControllerSetPlayerIndex(gamepad, -1);
}
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
}
static int NumJoysticks(void)
{
const int num_joysticks = SDL_NumJoysticks();
return MAX(0, num_joysticks);
}
void I_OpenGamepad(int device_index);
void I_InitGamepad(void)
{
UpdatePlatform();
@ -460,63 +478,196 @@ void I_InitGamepad(void)
I_Printf(VB_INFO, "I_InitGamepad: Initialize gamepad.");
I_AtExit(I_ShutdownGamepad, true);
}
void I_OpenGamepad(int which)
{
if (gamepad)
if (joy_device > 0)
{
return;
}
if (SDL_IsGameController(which))
{
gamepad = SDL_GameControllerOpen(which);
if (gamepad)
if (SDL_IsGameController(joy_device - 1))
{
I_Printf(VB_INFO,
"I_OpenGamepad: Found a valid gamepad, named: %s",
SDL_GameControllerName(gamepad));
I_SetRumbleSupported(gamepad);
I_ResetGamepad();
I_LoadGyroCalibration();
UpdatePlatform();
EnableGamepadEvents();
I_OpenGamepad(joy_device - 1);
}
else
{
joy_device = 1;
MN_UpdateAllGamepadItems();
}
}
if (gamepad == NULL)
else
{
I_Printf(VB_ERROR,
"I_OpenGamepad: Could not open gamepad %i: %s",
which, SDL_GetError());
MN_UpdateAllGamepadItems();
}
last_joy_device = joy_device;
SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED);
}
static boolean CheckActiveGamepad(void)
{
if (gamepad_instance_id != -1)
{
const int num_joysticks = NumJoysticks();
for (int i = 0; i < num_joysticks; i++)
{
if (SDL_JoystickGetDeviceInstanceID(i) == gamepad_instance_id)
{
joy_device = i + 1;
last_joy_device = joy_device;
MN_UpdateAllGamepadItems();
return true;
}
}
}
return false;
}
static void CloseGamepad(void)
{
if (gamepad_instance_id != -1)
{
I_ResetAllRumbleChannels();
I_SetRumbleSupported(NULL);
SDL_GameControllerSetPlayerIndex(gamepad, -1);
SDL_GameControllerClose(gamepad);
gamepad = NULL;
gamepad_instance_id = -1;
joy_device = 0;
last_joy_device = joy_device;
DisableGamepadEvents();
UpdatePlatform();
I_ResetGamepad();
}
}
void I_OpenGamepad(int device_index)
{
if (CheckActiveGamepad())
{
// Ignore when already using a gamepad.
return;
}
CloseGamepad();
gamepad = SDL_GameControllerOpen(device_index);
if (gamepad)
{
I_Printf(VB_INFO, "I_OpenGamepad: Found a valid gamepad, named: %s",
SDL_GameControllerName(gamepad));
gamepad_instance_id =
SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad));
joy_device = device_index + 1;
last_joy_device = joy_device;
I_SetRumbleSupported(gamepad);
I_ResetAllRumbleChannels();
I_ResetGamepad();
UpdatePlatform();
EnableGamepadEvents();
SDL_GameControllerSetPlayerIndex(gamepad, 0);
if (gyro_supported)
{
I_LoadGyroCalibration();
}
}
else
{
I_Printf(VB_ERROR, "I_OpenGamepad: Could not open gamepad %d: %s",
device_index, SDL_GetError());
}
MN_UpdateAllGamepadItems();
}
void I_CloseGamepad(SDL_JoystickID instance_id)
{
if (gamepad == NULL)
if (gamepad_instance_id != -1)
{
if (instance_id == gamepad_instance_id)
{
CloseGamepad();
if (NumJoysticks() && SDL_IsGameController(0))
{
// Fall back to another detected gamepad.
I_OpenGamepad(0);
return;
}
}
else if (CheckActiveGamepad())
{
// Ignore when already using a gamepad.
return;
}
}
MN_UpdateAllGamepadItems();
}
void I_UpdateGamepadDevice(boolean gamepad_input)
{
if (gamepad_input && joy_device == 0)
{
// Prevent accidentally disabling gamepad when it's being used.
joy_device = last_joy_device;
return;
}
SDL_JoystickID active_instance_id =
SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(gamepad));
last_joy_device = joy_device;
const int device_index = joy_device - 1;
CloseGamepad();
if (instance_id == active_instance_id)
if (device_index >= 0)
{
I_OpenGamepad(device_index);
}
else
{
SDL_GameControllerClose(gamepad);
gamepad = NULL;
I_SetRumbleSupported(NULL);
DisableGamepadEvents();
UpdatePlatform();
I_ResetGamepad();
MN_UpdateAllGamepadItems();
}
}
const char **I_GamepadDeviceList(void)
{
if (!I_GamepadEnabled())
{
return NULL;
}
array_free(gamepad_strings);
array_push(gamepad_strings, "None");
const int num_joysticks = NumJoysticks();
int num_gamepads = 0;
for (int i = 0; i < num_joysticks; i++)
{
if (SDL_IsGameController(i))
{
char *name = M_StringDuplicate(SDL_GameControllerNameForIndex(i));
array_push(gamepad_strings, name);
num_gamepads++;
}
}
if (joy_device > num_gamepads)
{
for (int i = num_gamepads; i < joy_device; i++)
{
array_push(gamepad_strings, "None");
}
}
return gamepad_strings;
}
boolean I_GamepadDevices(void)
{
return (array_size(gamepad_strings) > 1
&& strncmp(gamepad_strings[1], "None", strlen(gamepad_strings[1])));
}
static uint64_t GetSensorTimeUS(const SDL_ControllerSensorEvent *csensor)
{
#if SDL_VERSION_ATLEAST(2, 26, 0)

View File

@ -32,8 +32,11 @@ void I_FlushGamepadEvents(void);
void I_SetSensorEventState(boolean condition);
void I_SetSensorsEnabled(boolean condition);
void I_InitGamepad(void);
void I_OpenGamepad(int which);
void I_OpenGamepad(int device_index);
void I_CloseGamepad(SDL_JoystickID instance_id);
void I_UpdateGamepadDevice(boolean gamepad_input);
const char **I_GamepadDeviceList(void);
boolean I_GamepadDevices(void);
void I_ReadMouse(void);
void I_ReadGyro(void);

View File

@ -351,6 +351,7 @@ enum
str_mouse_accel,
str_gamepad_device,
str_gyro_space,
str_gyro_action,
str_gyro_sens,
@ -2900,15 +2901,34 @@ static const char *curve_strings[] = {
static void MN_PadAdv(void);
static void MN_Gyro(void);
static void UpdateGamepadDevice(void)
{
I_UpdateGamepadDevice(menu_input == pad_mode);
}
static setup_menu_t gen_settings4[] = {
{"Device", S_CHOICE | S_ACTION | S_WRAP_LINE, CNTR_X, M_SPC * 2,
{"joy_device"}, .strings_id = str_gamepad_device,
.action = UpdateGamepadDevice},
MI_GAP_Y(1),
{"Turn Speed", S_THERMO | S_THRM_SIZE11, CNTR_X, M_THRM_SPC,
{"joy_turn_speed"}, .action = I_ResetGamepad},
{"Look Speed", S_THERMO | S_THRM_SIZE11, CNTR_X, M_THRM_SPC,
{"joy_look_speed"}, .action = I_ResetGamepad},
MI_GAP_Y(4),
MI_GAP_Y(2),
{"Free Look", S_ONOFF, CNTR_X, M_SPC, {"padlook"},
.action = MN_UpdatePadLook},
{"Invert Look", S_ONOFF, CNTR_X, M_SPC, {"joy_invert_look"},
.action = I_ResetGamepad},
MI_GAP_Y(2),
{"Movement Deadzone", S_THERMO | S_PCT, CNTR_X, M_THRM_SPC,
{"joy_movement_inner_deadzone"}, .action = I_ResetGamepad},
@ -2916,20 +2936,10 @@ static setup_menu_t gen_settings4[] = {
{"Camera Deadzone", S_THERMO | S_PCT, CNTR_X, M_THRM_SPC,
{"joy_camera_inner_deadzone"}, .action = I_ResetGamepad},
MI_GAP_Y(4),
{"Rumble", S_THERMO, CNTR_X, M_THRM_SPC, {"joy_rumble"},
.strings_id = str_percent, .action = UpdateRumble},
MI_GAP_Y(5),
{"Free Look", S_ONOFF, CNTR_X, M_SPC, {"padlook"},
.action = MN_UpdatePadLook},
{"Invert Look", S_ONOFF, CNTR_X, M_SPC, {"joy_invert_look"},
.action = I_ResetGamepad},
MI_GAP_Y(8),
MI_GAP_Y(2),
{"Advanced Options", S_FUNC, CNTR_X, M_SPC, .action = MN_PadAdv},
@ -3037,13 +3047,15 @@ void MN_DrawPadAdv(void)
static void UpdateGamepadItems(void)
{
const boolean gamepad = (I_UseGamepad() && I_GamepadEnabled());
const boolean devices = (I_GamepadEnabled() && I_GamepadDevices());
const boolean gamepad = (I_UseGamepad() && devices);
const boolean gyro = (I_GyroEnabled() && I_GyroSupported());
const boolean sticks = I_UseStickLayout();
const boolean flick = (gamepad && sticks && !I_StandardLayout());
const boolean ramp = (gamepad && sticks && I_RampTimeEnabled());
const boolean condition = (!gamepad || !sticks);
DisableItem(!devices, gen_settings4, "joy_device");
DisableItem(!gamepad, gen_settings4, "Advanced Options");
DisableItem(!gamepad || !I_GyroSupported(), gen_settings4, "Gyro Options");
DisableItem(!gamepad || !I_RumbleSupported(), gen_settings4, "joy_rumble");
@ -3194,13 +3206,6 @@ static void UpdateGyroItems(void)
DisableItem(condition, gyro_settings1, "Calibrate");
}
void MN_UpdateAllGamepadItems(void)
{
UpdateWeaponSlotSelection();
UpdateGamepadItems();
UpdateGyroItems();
}
static setup_tab_t gyro_tabs[] = {{"Gyro"}, {NULL}};
static void MN_Gyro(void)
@ -4813,6 +4818,7 @@ static const char **selectstrings[] = {
NULL, // str_resampler
equalizer_preset_strings,
NULL, // str_mouse_accel
NULL, // str_gamepad_device
gyro_space_strings,
gyro_action_strings,
NULL, // str_gyro_sens
@ -4837,6 +4843,19 @@ static const char **GetStrings(int id)
return NULL;
}
static const char **GetGamepadDeviceStrings(void)
{
return I_GamepadDeviceList();
}
void MN_UpdateAllGamepadItems(void)
{
selectstrings[str_gamepad_device] = GetGamepadDeviceStrings();
UpdateWeaponSlotSelection();
UpdateGamepadItems();
UpdateGyroItems();
}
static void UpdateWeaponSlotStrings(void)
{
selectstrings[str_weapon_slots] = GetWeaponSlotStrings();
@ -4879,6 +4898,7 @@ void MN_InitMenuStrings(void)
selectstrings[str_mouse_accel] = GetMouseAccelStrings();
selectstrings[str_ms_time] = GetMsTimeStrings();
selectstrings[str_movement_sensitivity] = GetMovementSensitivityStrings();
selectstrings[str_gamepad_device] = GetGamepadDeviceStrings();
selectstrings[str_gyro_sens] = GetGyroSensitivityStrings();
selectstrings[str_gyro_accel] = GetGyroAccelStrings();
selectstrings[str_resampler] = GetResamplerStrings();