diff --git a/src/i_gamepad.c b/src/i_gamepad.c index b0f1fa4b..e3cec48a 100644 --- a/src/i_gamepad.c +++ b/src/i_gamepad.c @@ -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)"); diff --git a/src/i_gamepad.h b/src/i_gamepad.h index ea2fad04..5700c841 100644 --- a/src/i_gamepad.h +++ b/src/i_gamepad.h @@ -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. diff --git a/src/i_input.c b/src/i_input.c index 8fdb1b58..cc10c2d1 100644 --- a/src/i_input.c +++ b/src/i_input.c @@ -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) diff --git a/src/i_input.h b/src/i_input.h index ab24026c..0eaf6af4 100644 --- a/src/i_input.h +++ b/src/i_input.h @@ -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); diff --git a/src/mn_setup.c b/src/mn_setup.c index acaf6194..b6636cd5 100644 --- a/src/mn_setup.c +++ b/src/mn_setup.c @@ -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();