From 2bb1b33d7ec222afeeaab737b4f6a361b92eff1d Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sat, 7 May 2022 21:26:50 -0400 Subject: [PATCH 1/5] Full transition to use only SDL_GameController for gamepad This removes the usage of SDL_Joystick for joystick buttons and hat, and relies exclusively on SDL_GameController for input. This allows to identify buttons in a consistent manner across multiple types of controllers, which in turn allows to assign specific tasks to them without worrying of breaking usability, like having "A" to accept and "B" to cancel in menus. Init and Shutdown logic of game controller have been separated from main SDL Init and Shutdown functions. Old style button names "JOYx" have been removed. If your gamepad has paddles or other extra buttons, you'll need at least SDL version 2.0.14 to use them. --- src/client/cl_keyboard.c | 128 +++++------- src/client/header/keyboard.h | 154 ++++++-------- src/client/input/sdl.c | 387 ++++++++++++++++++----------------- src/client/menu/menu.c | 46 +---- 4 files changed, 312 insertions(+), 403 deletions(-) diff --git a/src/client/cl_keyboard.c b/src/client/cl_keyboard.c index e0aac061..a14df417 100644 --- a/src/client/cl_keyboard.c +++ b/src/client/cl_keyboard.c @@ -135,89 +135,59 @@ keyname_t keynames[] = { {"MWHEELUP", K_MWHEELUP}, {"MWHEELDOWN", K_MWHEELDOWN}, - {"JOY1", K_JOY1}, - {"JOY2", K_JOY2}, - {"JOY3", K_JOY3}, - {"JOY4", K_JOY4}, - {"JOY5", K_JOY5}, - {"JOY6", K_JOY6}, - {"JOY7", K_JOY7}, - {"JOY8", K_JOY8}, - {"JOY9", K_JOY9}, - {"JOY10", K_JOY10}, - {"JOY11", K_JOY11}, - {"JOY12", K_JOY12}, - {"JOY13", K_JOY13}, - {"JOY14", K_JOY14}, - {"JOY15", K_JOY15}, - {"JOY16", K_JOY16}, - {"JOY17", K_JOY17}, - {"JOY18", K_JOY18}, - {"JOY19", K_JOY19}, - {"JOY20", K_JOY20}, - {"JOY21", K_JOY21}, - {"JOY22", K_JOY22}, - {"JOY23", K_JOY23}, - {"JOY24", K_JOY24}, - {"JOY25", K_JOY25}, - {"JOY26", K_JOY26}, - {"JOY27", K_JOY27}, - {"JOY28", K_JOY28}, - {"JOY29", K_JOY29}, - {"JOY30", K_JOY30}, - {"JOY31", K_JOY31}, - {"JOY32", K_JOY32}, - - {"HAT_UP", K_HAT_UP}, - {"HAT_RIGHT", K_HAT_RIGHT}, - {"HAT_DOWN", K_HAT_DOWN}, - {"HAT_LEFT", K_HAT_LEFT}, - + {"BTN_A", K_BTN_A}, + {"BTN_B", K_BTN_B}, + {"BTN_X", K_BTN_X}, + {"BTN_Y", K_BTN_Y}, + {"STICK_LEFT", K_STICK_LEFT}, + {"STICK_RIGHT", K_STICK_RIGHT}, + {"SHOULDR_LEFT", K_SHOULDER_LEFT}, + {"SHOULDR_RIGHT", K_SHOULDER_RIGHT}, {"TRIG_LEFT", K_TRIG_LEFT}, {"TRIG_RIGHT", K_TRIG_RIGHT}, + {"DP_UP", K_DPAD_UP}, + {"DP_DOWN", K_DPAD_DOWN}, + {"DP_LEFT", K_DPAD_LEFT}, + {"DP_RIGHT", K_DPAD_RIGHT}, + + {"PADDLE_1", K_PADDLE_1}, + {"PADDLE_2", K_PADDLE_2}, + {"PADDLE_3", K_PADDLE_3}, + {"PADDLE_4", K_PADDLE_4}, + {"BTN_MISC1", K_BTN_MISC1}, + {"TOUCHPAD", K_TOUCHPAD}, + {"BTN_BACK", K_BTN_BACK}, + {"BTN_GUIDE", K_BTN_GUIDE}, + {"BTN_START", K_BTN_START}, + // virtual keys you get by pressing the corresponding normal joy key // and the altselector key - {"JOY1_ALT", K_JOY1_ALT}, - {"JOY2_ALT", K_JOY2_ALT}, - {"JOY3_ALT", K_JOY3_ALT}, - {"JOY4_ALT", K_JOY4_ALT}, - {"JOY5_ALT", K_JOY5_ALT}, - {"JOY6_ALT", K_JOY6_ALT}, - {"JOY7_ALT", K_JOY7_ALT}, - {"JOY8_ALT", K_JOY8_ALT}, - {"JOY9_ALT", K_JOY9_ALT}, - {"JOY10_ALT", K_JOY10_ALT}, - {"JOY11_ALT", K_JOY11_ALT}, - {"JOY12_ALT", K_JOY12_ALT}, - {"JOY13_ALT", K_JOY13_ALT}, - {"JOY14_ALT", K_JOY14_ALT}, - {"JOY15_ALT", K_JOY15_ALT}, - {"JOY16_ALT", K_JOY16_ALT}, - {"JOY17_ALT", K_JOY17_ALT}, - {"JOY18_ALT", K_JOY18_ALT}, - {"JOY19_ALT", K_JOY19_ALT}, - {"JOY20_ALT", K_JOY20_ALT}, - {"JOY21_ALT", K_JOY21_ALT}, - {"JOY22_ALT", K_JOY22_ALT}, - {"JOY23_ALT", K_JOY23_ALT}, - {"JOY24_ALT", K_JOY24_ALT}, - {"JOY25_ALT", K_JOY25_ALT}, - {"JOY26_ALT", K_JOY26_ALT}, - {"JOY27_ALT", K_JOY27_ALT}, - {"JOY28_ALT", K_JOY28_ALT}, - {"JOY29_ALT", K_JOY29_ALT}, - {"JOY30_ALT", K_JOY30_ALT}, - {"JOY31_ALT", K_JOY31_ALT}, - {"JOY32_ALT", K_JOY32_ALT}, + {"BTN_A_ALT", K_BTN_A_ALT}, + {"BTN_B_ALT", K_BTN_B_ALT}, + {"BTN_X_ALT", K_BTN_X_ALT}, + {"BTN_Y_ALT", K_BTN_Y_ALT}, + {"STICK_LEFT_ALT", K_STICK_LEFT_ALT}, + {"STICK_RIGHT_ALT", K_STICK_RIGHT_ALT}, + {"SHOULDR_LEFT_ALT", K_SHOULDER_LEFT_ALT}, + {"SHOULDR_RIGHT_ALT", K_SHOULDER_RIGHT_ALT}, + {"TRIG_LEFT_ALT", K_TRIG_LEFT_ALT}, + {"TRIG_RIGHT_ALT", K_TRIG_RIGHT_ALT}, - {"HAT_UP_ALT", K_HAT_UP_ALT}, - {"HAT_RIGHT_ALT", K_HAT_RIGHT_ALT}, - {"HAT_DOWN_ALT", K_HAT_DOWN_ALT}, - {"HAT_LEFT_ALT", K_HAT_LEFT_ALT}, + {"DP_UP_ALT", K_DPAD_UP_ALT}, + {"DP_DOWN_ALT", K_DPAD_DOWN_ALT}, + {"DP_LEFT_ALT", K_DPAD_LEFT_ALT}, + {"DP_RIGHT_ALT", K_DPAD_RIGHT_ALT}, - {"TRIG_LEFT", K_TRIG_LEFT_ALT}, - {"TRIG_RIGHT", K_TRIG_RIGHT_ALT}, + {"PADDLE_1_ALT", K_PADDLE_1_ALT}, + {"PADDLE_2_ALT", K_PADDLE_2_ALT}, + {"PADDLE_3_ALT", K_PADDLE_3_ALT}, + {"PADDLE_4_ALT", K_PADDLE_4_ALT}, + {"BTN_MISC1_ALT", K_BTN_MISC1_ALT}, + {"TOUCHPAD_ALT", K_TOUCHPAD_ALT}, + {"BTN_BACK_ALT", K_BTN_BACK_ALT}, + {"BTN_GUIDE_ALT", K_BTN_GUIDE_ALT}, + {"BTN_START_ALT", K_BTN_START_ALT}, {"JOY_BACK", K_JOY_BACK}, @@ -1194,13 +1164,13 @@ Key_Event(int key, qboolean down, qboolean special) cvar_t *fullscreen; unsigned int time = Sys_Milliseconds(); - // evil hack for the joystick key altselector, which turns K_JOYx into K_JOYx_ALT - if(joy_altselector_pressed && key >= K_JOY1 && key <= K_JOY_LAST_REGULAR) + // evil hack for the joystick key altselector, which turns K_BTN_x into K_BTN_x_ALT + if(joy_altselector_pressed && key >= K_JOY_FIRST_REGULAR && key <= K_JOY_LAST_REGULAR) { // make sure key is not the altselector itself (which we won't turn into *_ALT) if(keybindings[key] == NULL || strcmp(keybindings[key], "+joyaltselector") != 0) { - int altkey = key + (K_JOY1_ALT - K_JOY1); + int altkey = key + (K_JOY_FIRST_REGULAR_ALT - K_JOY_FIRST_REGULAR); // allow fallback to binding with non-alt key if(keybindings[altkey] != NULL || keybindings[key] == NULL) key = altkey; diff --git a/src/client/header/keyboard.h b/src/client/header/keyboard.h index 77c98c16..d0ffe9f1 100644 --- a/src/client/header/keyboard.h +++ b/src/client/header/keyboard.h @@ -42,7 +42,7 @@ /* * the joystick altselector key is pressed - * => K_JOYx turns into K_JOYx_ALT + * => K_BTN_x turns into K_BTN_x_ALT */ extern qboolean joy_altselector_pressed; @@ -125,98 +125,6 @@ enum QKEYS { K_MWHEELDOWN, K_MWHEELUP, - K_JOY1, - K_JOY2, - K_JOY3, - K_JOY4, - K_JOY5, - K_JOY6, - K_JOY7, - K_JOY8, - K_JOY9, - K_JOY10, - K_JOY11, - K_JOY12, - K_JOY13, - K_JOY14, - K_JOY15, - K_JOY16, - K_JOY17, - K_JOY18, - K_JOY19, - K_JOY20, - K_JOY21, - K_JOY22, - K_JOY23, - K_JOY24, - K_JOY25, - K_JOY26, - K_JOY27, - K_JOY28, - K_JOY29, - K_JOY30, - K_JOY31, - K_JOY32, - - K_HAT_UP, - K_HAT_RIGHT, - K_HAT_DOWN, - K_HAT_LEFT, - - K_TRIG_LEFT, - K_TRIG_RIGHT, - - // add other joystick/controller keys before this one - // and adjust it accordingly, also remember to add corresponding _ALT key below! - K_JOY_LAST_REGULAR = K_TRIG_RIGHT, - - /* Can't be mapped to any action (=> not regular) */ - K_JOY_BACK, - - K_JOY1_ALT, - K_JOY2_ALT, - K_JOY3_ALT, - K_JOY4_ALT, - K_JOY5_ALT, - K_JOY6_ALT, - K_JOY7_ALT, - K_JOY8_ALT, - K_JOY9_ALT, - K_JOY10_ALT, - K_JOY11_ALT, - K_JOY12_ALT, - K_JOY13_ALT, - K_JOY14_ALT, - K_JOY15_ALT, - K_JOY16_ALT, - K_JOY17_ALT, - K_JOY18_ALT, - K_JOY19_ALT, - K_JOY20_ALT, - K_JOY21_ALT, - K_JOY22_ALT, - K_JOY23_ALT, - K_JOY24_ALT, - K_JOY25_ALT, - K_JOY26_ALT, - K_JOY27_ALT, - K_JOY28_ALT, - K_JOY29_ALT, - K_JOY30_ALT, - K_JOY31_ALT, - K_JOY32_ALT, - - K_HAT_UP_ALT, - K_HAT_RIGHT_ALT, - K_HAT_DOWN_ALT, - K_HAT_LEFT_ALT, - - K_TRIG_LEFT_ALT, - K_TRIG_RIGHT_ALT, - - // add other joystick/controller keys before this one and adjust it accordingly - K_JOY_LAST_REGULAR_ALT = K_TRIG_RIGHT_ALT, - K_SUPER, // TODO: what is this? SDL doesn't seem to know it.. K_COMPOSE, K_MODE, @@ -296,6 +204,66 @@ enum QKEYS { // want to be able to type in the console) - the user can't bind this key. K_CONSOLE, + // Keyboard keys / codes end here. Any new ones should go before this. + // From here on, only gamepad controls must be allowed. + + K_BTN_A, + K_JOY_FIRST_REGULAR = K_BTN_A, + K_BTN_B, + K_BTN_X, + K_BTN_Y, + K_BTN_BACK, + K_BTN_GUIDE, + K_BTN_START, + K_STICK_LEFT, + K_STICK_RIGHT, + K_SHOULDER_LEFT, + K_SHOULDER_RIGHT, + K_TRIG_LEFT, + K_TRIG_RIGHT, + K_DPAD_UP, + K_DPAD_DOWN, + K_DPAD_LEFT, + K_DPAD_RIGHT, + K_BTN_MISC1, + K_PADDLE_1, + K_PADDLE_2, + K_PADDLE_3, + K_PADDLE_4, + K_TOUCHPAD, + + // add other joystick/controller keys before this one + // and adjust it accordingly, also remember to add corresponding _ALT key below! + K_JOY_LAST_REGULAR = K_TOUCHPAD, + + /* Can't be mapped to any action (=> not regular) */ + K_JOY_BACK, + + K_BTN_A_ALT, + K_JOY_FIRST_REGULAR_ALT = K_BTN_A_ALT, + K_BTN_B_ALT, + K_BTN_X_ALT, + K_BTN_Y_ALT, + K_BTN_BACK_ALT, + K_BTN_GUIDE_ALT, + K_BTN_START_ALT, + K_STICK_LEFT_ALT, + K_STICK_RIGHT_ALT, + K_SHOULDER_LEFT_ALT, + K_SHOULDER_RIGHT_ALT, + K_TRIG_LEFT_ALT, + K_TRIG_RIGHT_ALT, + K_DPAD_UP_ALT, + K_DPAD_DOWN_ALT, + K_DPAD_LEFT_ALT, + K_DPAD_RIGHT_ALT, + K_BTN_MISC1_ALT, + K_PADDLE_1_ALT, + K_PADDLE_2_ALT, + K_PADDLE_3_ALT, + K_PADDLE_4_ALT, + K_TOUCHPAD_ALT, + K_LAST }; diff --git a/src/client/input/sdl.c b/src/client/input/sdl.c index c97fd0a5..15362369 100644 --- a/src/client/input/sdl.c +++ b/src/client/input/sdl.c @@ -47,7 +47,6 @@ // IN_Update() called at the beginning of a frame to the // actual movement functions called at a later time. static float mouse_x, mouse_y; -static int back_button_id = -1; static int sdl_back_button = SDL_CONTROLLER_BUTTON_BACK; static float joystick_yaw, joystick_pitch; static float joystick_forwardmove, joystick_sidemove; @@ -58,7 +57,7 @@ static qboolean mlooking; // Used throughout the client. int sys_frame_time; -// the joystick altselector that turns K_JOYX into K_JOYX_ALT +// the joystick altselector that turns K_BTN_X into K_BTN_X_ALT // is pressed qboolean joy_altselector_pressed = false; @@ -95,7 +94,6 @@ struct hapric_effects_cache { qboolean show_haptic; static SDL_Haptic *joystick_haptic = NULL; -static SDL_Joystick *joystick = NULL; static SDL_GameController *controller = NULL; #define HAPTIC_EFFECT_LIST_SIZE 16 @@ -421,6 +419,45 @@ IN_TranslateScancodeToQ2Key(SDL_Scancode sc) return 0; } +static int +IN_TranslateGamepadBtnToQ2Key(int btn) +{ + +#define MY_BTN_CASE(X,Y) case SDL_CONTROLLER_BUTTON_ ## X : return K_ ## Y; + + switch( btn ) + { + // case SDL_CONTROLLER_BUTTON_A : return K_BTN_A; + MY_BTN_CASE(A,BTN_A) + MY_BTN_CASE(B,BTN_B) + MY_BTN_CASE(X,BTN_X) + MY_BTN_CASE(Y,BTN_Y) + MY_BTN_CASE(LEFTSHOULDER,SHOULDER_LEFT) + MY_BTN_CASE(RIGHTSHOULDER,SHOULDER_RIGHT) + MY_BTN_CASE(LEFTSTICK,STICK_LEFT) + MY_BTN_CASE(RIGHTSTICK,STICK_RIGHT) + MY_BTN_CASE(DPAD_UP,DPAD_UP) + MY_BTN_CASE(DPAD_DOWN,DPAD_DOWN) + MY_BTN_CASE(DPAD_LEFT,DPAD_LEFT) + MY_BTN_CASE(DPAD_RIGHT,DPAD_RIGHT) +#if SDL_VERSION_ATLEAST(2, 0, 14) // support for newer buttons + MY_BTN_CASE(PADDLE1,PADDLE_1) + MY_BTN_CASE(PADDLE2,PADDLE_2) + MY_BTN_CASE(PADDLE3,PADDLE_3) + MY_BTN_CASE(PADDLE4,PADDLE_4) + MY_BTN_CASE(MISC1,BTN_MISC1) + MY_BTN_CASE(TOUCHPAD,TOUCHPAD) +#endif + MY_BTN_CASE(BACK,BTN_BACK) + MY_BTN_CASE(GUIDE,BTN_GUIDE) + MY_BTN_CASE(START,BTN_START) + } + +#undef MY_BTN_CASE + + return 0; +} + /* ------------------------------------------------------------------ */ /* @@ -435,7 +472,6 @@ IN_Update(void) SDL_Event event; unsigned int key; - static char last_hat = SDL_HAT_CENTERED; static qboolean left_trigger = false; static qboolean right_trigger = false; @@ -583,13 +619,22 @@ IN_Update(void) break; case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERBUTTONDOWN: /* Handle Controller Back button */ + case SDL_CONTROLLERBUTTONDOWN: { qboolean down = (event.type == SDL_CONTROLLERBUTTONDOWN); + // Handle Back Button first, to override its original key if (event.cbutton.button == sdl_back_button) { Key_Event(K_JOY_BACK, down, true); + break; + } + + key = IN_TranslateGamepadBtnToQ2Key(event.cbutton.button); + if(key != 0) + { + Key_Event(key, down, true); + } break; @@ -727,58 +772,6 @@ IN_Update(void) break; } - // Joystick can have more buttons than on general game controller - // so try to map not free buttons - case SDL_JOYBUTTONUP: - case SDL_JOYBUTTONDOWN: - { - qboolean down = (event.type == SDL_JOYBUTTONDOWN); - - // Ignore back button, we don't need event for such button - if (back_button_id == event.jbutton.button) - { - return; - } - - if (event.jbutton.button <= (K_JOY32 - K_JOY1)) - { - Key_Event(event.jbutton.button + K_JOY1, down, true); - } - - break; - } - - case SDL_JOYHATMOTION: - { - if (last_hat != event.jhat.value) - { - char diff = last_hat ^event.jhat.value; - int i; - - for (i = 0; i < 4; i++) - { - if (diff & (1 << i)) - { - // check that we have button up for some bit - if (last_hat & (1 << i)) - { - Key_Event(i + K_HAT_UP, false, true); - } - - /* check that we have button down for some bit */ - if (event.jhat.value & (1 << i)) - { - Key_Event(i + K_HAT_UP, true, true); - } - } - } - - last_hat = event.jhat.value; - } - - break; - } - case SDL_QUIT: Com_Quit(); break; @@ -1021,7 +1014,7 @@ IN_Haptic_Effects_Info(void) { show_haptic = true; - Com_Printf ("Joystic/Mouse haptic:\n"); + Com_Printf ("Joystick/Mouse haptic:\n"); Com_Printf (" * %d effects\n", SDL_HapticNumEffects(joystick_haptic)); Com_Printf (" * %d effects in same time\n", SDL_HapticNumEffectsPlaying(joystick_haptic)); Com_Printf (" * %d haptic axis\n", SDL_HapticNumAxes(joystick_haptic)); @@ -1195,15 +1188,141 @@ Haptic_Feedback(char *name, int effect_volume, int effect_duration, } } +/* + * Game Controller + */ +static void +IN_Controller_Init(void) +{ + cvar_t *in_sdlbackbutton; + int nummappings; + char controllerdb[MAX_OSPATH] = {0}; + SDL_Joystick *joystick = NULL; + SDL_bool is_controller = SDL_FALSE; + + in_sdlbackbutton = Cvar_Get("in_sdlbackbutton", "0", CVAR_ARCHIVE); + if (in_sdlbackbutton) + { + switch ((int)in_sdlbackbutton->value) + { + case 1: + sdl_back_button = SDL_CONTROLLER_BUTTON_START; + break; + case 2: + sdl_back_button = SDL_CONTROLLER_BUTTON_GUIDE; + break; + default: + sdl_back_button = SDL_CONTROLLER_BUTTON_BACK; + } + } + + if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC)) + { + if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == -1) + { + Com_Printf ("Couldn't init SDL joystick: %s.\n", SDL_GetError ()); + return; + } + } + + Com_Printf ("%i joysticks were found.\n", SDL_NumJoysticks()); + + if (!SDL_NumJoysticks()) + { + joystick_haptic = SDL_HapticOpenFromMouse(); + + if (joystick_haptic == NULL) + { + Com_Printf("Most likely mouse isn't haptic.\n"); + } + else + { + IN_Haptic_Effects_Info(); + } + + return; + } + + for (const char* rawPath = FS_GetNextRawPath(NULL); rawPath != NULL; rawPath = FS_GetNextRawPath(rawPath)) + { + snprintf(controllerdb, MAX_OSPATH, "%s/gamecontrollerdb.txt", rawPath); + nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); + if (nummappings > 0) + Com_Printf ("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); + } + + for (int i = 0; i < SDL_NumJoysticks(); i++) + { + joystick = SDL_JoystickOpen(i); + const char* joystick_name = SDL_JoystickName(joystick); + + Com_Printf ("The name of the joystick is '%s'\n", joystick_name); + Com_Printf ("Number of Axes: %d\n", SDL_JoystickNumAxes(joystick)); + Com_Printf ("Number of Buttons: %d\n", SDL_JoystickNumButtons(joystick)); + Com_Printf ("Number of Balls: %d\n", SDL_JoystickNumBalls(joystick)); + Com_Printf ("Number of Hats: %d\n", SDL_JoystickNumHats(joystick)); + + is_controller = SDL_IsGameController(i); + if (!is_controller) + { + char joystick_guid[256] = {0}; + + SDL_JoystickGUID guid; + guid = SDL_JoystickGetDeviceGUID(i); + + SDL_JoystickGetGUIDString(guid, joystick_guid, 255); + + Com_Printf ("To use joystick as game controller please set SDL_GAMECONTROLLERCONFIG:\n"); + Com_Printf ("e.g.: SDL_GAMECONTROLLERCONFIG='%s,%s,leftx:a0,lefty:a1,rightx:a2,righty:a3,back:b1,...\n", joystick_guid, joystick_name); + Com_Printf ("Or you can put 'gamecontrollerdb.txt' in your game directory.\n"); + } + + SDL_JoystickClose(joystick); + joystick = NULL; + + if (is_controller) + { + controller = SDL_GameControllerOpen(i); + + Com_Printf ("Controller settings: %s\n", SDL_GameControllerMapping(controller)); + Com_Printf ("Controller axis: \n"); + Com_Printf (" * leftx = %s\n", joy_axis_leftx->string); + Com_Printf (" * lefty = %s\n", joy_axis_lefty->string); + Com_Printf (" * rightx = %s\n", joy_axis_rightx->string); + Com_Printf (" * righty = %s\n", joy_axis_righty->string); + Com_Printf (" * triggerleft = %s\n", joy_axis_triggerleft->string); + Com_Printf (" * triggerright = %s\n", joy_axis_triggerright->string); + + Com_Printf ("Controller thresholds: \n"); + Com_Printf (" * leftx = %f\n", joy_axis_leftx_threshold->value); + Com_Printf (" * lefty = %f\n", joy_axis_lefty_threshold->value); + Com_Printf (" * rightx = %f\n", joy_axis_rightx_threshold->value); + Com_Printf (" * righty = %f\n", joy_axis_righty_threshold->value); + Com_Printf (" * triggerleft = %f\n", joy_axis_triggerleft_threshold->value); + Com_Printf (" * triggerright = %f\n", joy_axis_triggerright_threshold->value); + + joystick_haptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(controller)); + + if (joystick_haptic == NULL) + { + Com_Printf("Most likely controller isn't haptic.\n"); + } + else + { + IN_Haptic_Effects_Info(); + } + + break; + } + } +} + /* * Initializes the backend */ void IN_Init(void) { - cvar_t *in_sdlbackbutton; - int nummappings; - char controllerdb[MAX_OSPATH] = {0}; Com_Printf("------- input initialization -------\n"); mouse_x = mouse_y = 0; @@ -1246,22 +1365,6 @@ IN_Init(void) windowed_mouse = Cvar_Get("windowed_mouse", "1", CVAR_USERINFO | CVAR_ARCHIVE); - in_sdlbackbutton = Cvar_Get("in_sdlbackbutton", "0", CVAR_ARCHIVE); - if (in_sdlbackbutton) - { - switch ((int)in_sdlbackbutton->value) - { - case 1: - sdl_back_button = SDL_CONTROLLER_BUTTON_START; - break; - case 2: - sdl_back_button = SDL_CONTROLLER_BUTTON_GUIDE; - break; - default: - sdl_back_button = SDL_CONTROLLER_BUTTON_BACK; - } - } - Cmd_AddCommand("+mlook", IN_MLookDown); Cmd_AddCommand("-mlook", IN_MLookUp); @@ -1270,108 +1373,7 @@ IN_Init(void) SDL_StartTextInput(); - /* Joystick init */ - if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC)) - { - if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == -1) - { - Com_Printf ("Couldn't init SDL joystick: %s.\n", SDL_GetError ()); - } - else - { - Com_Printf ("%i joysticks were found.\n", SDL_NumJoysticks()); - - if (SDL_NumJoysticks() > 0) - { - for (const char* rawPath = FS_GetNextRawPath(NULL); rawPath != NULL; rawPath = FS_GetNextRawPath(rawPath)) - { - snprintf(controllerdb, MAX_OSPATH, "%s/gamecontrollerdb.txt", rawPath); - nummappings = SDL_GameControllerAddMappingsFromFile(controllerdb); - if (nummappings > 0) - Com_Printf ("%d mappings loaded from gamecontrollerdb.txt\n", nummappings); - } - - for (int i = 0; i < SDL_NumJoysticks(); i++) { - joystick = SDL_JoystickOpen(i); - - Com_Printf ("The name of the joystick is '%s'\n", SDL_JoystickName(joystick)); - Com_Printf ("Number of Axes: %d\n", SDL_JoystickNumAxes(joystick)); - Com_Printf ("Number of Buttons: %d\n", SDL_JoystickNumButtons(joystick)); - Com_Printf ("Number of Balls: %d\n", SDL_JoystickNumBalls(joystick)); - Com_Printf ("Number of Hats: %d\n", SDL_JoystickNumHats(joystick)); - - joystick_haptic = SDL_HapticOpenFromJoystick(joystick); - - if (joystick_haptic == NULL) - { - Com_Printf("Most likely joystick isn't haptic.\n"); - } - else - { - IN_Haptic_Effects_Info(); - } - - if(SDL_IsGameController(i)) - { - SDL_GameControllerButtonBind backBind; - controller = SDL_GameControllerOpen(i); - - Com_Printf ("Controller settings: %s\n", SDL_GameControllerMapping(controller)); - Com_Printf ("Controller axis: \n"); - Com_Printf (" * leftx = %s\n", joy_axis_leftx->string); - Com_Printf (" * lefty = %s\n", joy_axis_lefty->string); - Com_Printf (" * rightx = %s\n", joy_axis_rightx->string); - Com_Printf (" * righty = %s\n", joy_axis_righty->string); - Com_Printf (" * triggerleft = %s\n", joy_axis_triggerleft->string); - Com_Printf (" * triggerright = %s\n", joy_axis_triggerright->string); - - Com_Printf ("Controller thresholds: \n"); - Com_Printf (" * leftx = %f\n", joy_axis_leftx_threshold->value); - Com_Printf (" * lefty = %f\n", joy_axis_lefty_threshold->value); - Com_Printf (" * rightx = %f\n", joy_axis_rightx_threshold->value); - Com_Printf (" * righty = %f\n", joy_axis_righty_threshold->value); - Com_Printf (" * triggerleft = %f\n", joy_axis_triggerleft_threshold->value); - Com_Printf (" * triggerright = %f\n", joy_axis_triggerright_threshold->value); - - backBind = SDL_GameControllerGetBindForButton(controller, sdl_back_button); - - if (backBind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON) - { - back_button_id = backBind.value.button; - Com_Printf ("\nBack button JOY%d will be unbindable.\n", back_button_id+1); - } - - break; - } - else - { - char joystick_guid[256] = {0}; - - SDL_JoystickGUID guid; - guid = SDL_JoystickGetDeviceGUID(i); - - SDL_JoystickGetGUIDString(guid, joystick_guid, 255); - - Com_Printf ("To use joystick as game controller please set SDL_GAMECONTROLLERCONFIG:\n"); - Com_Printf ("e.g.: SDL_GAMECONTROLLERCONFIG='%s,%s,leftx:a0,lefty:a1,rightx:a2,righty:a3,back:b1,...\n", joystick_guid, SDL_JoystickName(joystick)); - } - } - } - else - { - joystick_haptic = SDL_HapticOpenFromMouse(); - - if (joystick_haptic == NULL) - { - Com_Printf("Most likely mouse isn't haptic.\n"); - } - else - { - IN_Haptic_Effects_Info(); - } - } - } - } + IN_Controller_Init(); Com_Printf("------------------------------------\n\n"); } @@ -1391,6 +1393,18 @@ IN_Haptic_Shutdown(void) } } +static void +IN_Controller_Shutdown(void) +{ + IN_Haptic_Shutdown(); + + if (controller) + { + SDL_GameControllerClose(controller); + controller = NULL; + } +} + void IN_Shutdown(void) { @@ -1400,20 +1414,7 @@ IN_Shutdown(void) Com_Printf("Shutting down input.\n"); - IN_Haptic_Shutdown(); - - if (controller) - { - back_button_id = -1; - SDL_GameControllerClose(controller); - controller = NULL; - } - - if (joystick) - { - SDL_JoystickClose(joystick); - joystick = NULL; - } + IN_Controller_Shutdown(); const Uint32 subsystems = SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC; if (SDL_WasInit(subsystems) == subsystems) diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index 25f9ce29..70b5be2a 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -229,25 +229,25 @@ Key_GetMenuKey(int key) { case K_KP_UPARROW: case K_UPARROW: - case K_HAT_UP: + case K_DPAD_UP: return K_UPARROW; case K_TAB: case K_KP_DOWNARROW: case K_DOWNARROW: - case K_HAT_DOWN: + case K_DPAD_DOWN: return K_DOWNARROW; case K_KP_LEFTARROW: case K_LEFTARROW: - case K_HAT_LEFT: - case K_TRIG_LEFT: + case K_DPAD_LEFT: + case K_SHOULDER_LEFT: return K_LEFTARROW; case K_KP_RIGHTARROW: case K_RIGHTARROW: - case K_HAT_RIGHT: - case K_TRIG_RIGHT: + case K_DPAD_RIGHT: + case K_SHOULDER_RIGHT: return K_RIGHTARROW; case K_MOUSE1: @@ -256,44 +256,14 @@ Key_GetMenuKey(int key) case K_MOUSE4: case K_MOUSE5: - case K_JOY1: - case K_JOY2: - case K_JOY3: - case K_JOY4: - case K_JOY5: - case K_JOY6: - case K_JOY7: - case K_JOY8: - case K_JOY9: - case K_JOY10: - case K_JOY11: - case K_JOY12: - case K_JOY13: - case K_JOY14: - case K_JOY15: - case K_JOY16: - case K_JOY17: - case K_JOY18: - case K_JOY19: - case K_JOY20: - case K_JOY21: - case K_JOY22: - case K_JOY23: - case K_JOY24: - case K_JOY25: - case K_JOY26: - case K_JOY27: - case K_JOY28: - case K_JOY29: - case K_JOY30: - case K_JOY31: - case K_KP_ENTER: case K_ENTER: + case K_BTN_A: return K_ENTER; case K_ESCAPE: case K_JOY_BACK: + case K_BTN_B: return K_ESCAPE; } From d4100f7113badcb907b7e8107ab6bd904ef27e98 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sat, 7 May 2022 22:45:44 -0400 Subject: [PATCH 2/5] Menu inputs standardised Streamlined menu inputs by making most "*_MenuKey(key)" functions to use Key_GetMenuKey(). "Backspace" is not a special case anymore, so any menus that have a "delete" option can already handle any input expected, like the backspace or delete keys, or the "Y" button of the controller, to run it. Also, fixed a bug where changing the key for "team chat" also changes the one for "chat" in the "multiplayer" -> "customize controls" menu. --- src/client/menu/menu.c | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index 70b5be2a..8cdc07e3 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -265,6 +265,12 @@ Key_GetMenuKey(int key) case K_JOY_BACK: case K_BTN_B: return K_ESCAPE; + + case K_BACKSPACE: + case K_DEL: + case K_KP_DEL: + case K_BTN_Y: + return K_BACKSPACE; } return key; @@ -800,9 +806,6 @@ static void M_UnbindCommand(char *command) { int j; - int l; - - l = strlen(command); for (j = 0; j < K_LAST; j++) { @@ -814,7 +817,7 @@ M_UnbindCommand(char *command) continue; } - if (!strncmp(b, command, l)) + if (!strcmp(b, command)) { Key_SetBinding(j, ""); } @@ -826,10 +829,8 @@ M_FindKeysForCommand(char *command, int *twokeys) { int count; int j; - int l; twokeys[0] = twokeys[1] = -1; - l = strlen(command); count = 0; for (j = 0; j < K_LAST; j++) @@ -842,7 +843,7 @@ M_FindKeysForCommand(char *command, int *twokeys) continue; } - if (!strncmp(b, command, l)) + if (!strcmp(b, command)) { twokeys[count] = j; count++; @@ -980,15 +981,13 @@ Keys_MenuKey(int key) return menu_out_sound; } + key = Key_GetMenuKey(key); switch (key) { - case K_KP_ENTER: case K_ENTER: KeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - case K_DEL: /* delete bindings */ - case K_KP_DEL: M_UnbindCommand(bindnames[item->generic.localdata[0]][0]); return menu_out_sound; default: @@ -1131,15 +1130,13 @@ MultiplayerKeys_MenuKey(int key) return menu_out_sound; } + key = Key_GetMenuKey(key); switch (key) { - case K_KP_ENTER: case K_ENTER: MultiplayerKeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - case K_DEL: /* delete bindings */ - case K_KP_DEL: M_UnbindCommand(multiplayer_key_bindnames[item->generic.localdata[0]][0]); return menu_out_sound; default: @@ -2862,8 +2859,6 @@ LoadGame_MenuKey(int key) return menu_move_sound; case K_BACKSPACE: - case K_DEL: - case K_KP_DEL: if ((item = Menu_ItemAtCursor(m)) != NULL) { if (item->type == MTYPE_ACTION) @@ -3034,8 +3029,6 @@ SaveGame_MenuKey(int key) return menu_move_sound; case K_BACKSPACE: - case K_DEL: - case K_KP_DEL: if ((item = Menu_ItemAtCursor(m)) != NULL) { if (item->type == MTYPE_ACTION) From 0aa44afa139f210427af4a554ef3a3329875823e Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sat, 7 May 2022 23:55:00 -0400 Subject: [PATCH 3/5] New customize controller buttons menu option Handles game controller button bindings exclusively. Classic "customize controls" option now handles only keyboard / mouse bindings. This separation is achieved with the new order of QKEYS in keyboard.h, and binding functions in menus (especially "MenuKey" functions) now take into account the "scope" where they operate: keyboard/mouse only, controller only, or both. --- src/client/header/keyboard.h | 1 + src/client/menu/header/qmenu.h | 6 + src/client/menu/menu.c | 234 ++++++++++++++++++++++++++++++--- 3 files changed, 224 insertions(+), 17 deletions(-) diff --git a/src/client/header/keyboard.h b/src/client/header/keyboard.h index d0ffe9f1..60ce807e 100644 --- a/src/client/header/keyboard.h +++ b/src/client/header/keyboard.h @@ -206,6 +206,7 @@ enum QKEYS { // Keyboard keys / codes end here. Any new ones should go before this. // From here on, only gamepad controls must be allowed. + // Otherwise, separate bindings (keyboard / controller) menu options will not work. K_BTN_A, K_JOY_FIRST_REGULAR = K_BTN_A, diff --git a/src/client/menu/header/qmenu.h b/src/client/menu/header/qmenu.h index 2cbf9eef..a8f3cde6 100644 --- a/src/client/menu/header/qmenu.h +++ b/src/client/menu/header/qmenu.h @@ -43,6 +43,12 @@ #define QMF_GRAYED 0x00000002 #define QMF_NUMBERSONLY 0x00000004 +enum { + KEYS_ALL = 0, + KEYS_KEYBOARD_MOUSE, + KEYS_CONTROLLER +}; + typedef struct _tag_menuframework { int x, y; diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index 8cdc07e3..bf996d4b 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -64,6 +64,7 @@ static void M_Menu_Video_f(void); static void M_Menu_Options_f(void); static void M_Menu_Keys_f(void); static void M_Menu_Joy_f(void); +static void M_Menu_ControllerButtons_f(void); static void M_Menu_Quit_f(void); void M_Menu_Credits(void); @@ -790,8 +791,7 @@ char *bindnames[][2] = {"invdrop", "drop item"}, {"invprev", "prev item"}, {"invnext", "next item"}, - {"cmd help", "help computer"}, - {"+joyaltselector", "enable alt joy keys"} + {"cmd help", "help computer"} }; #define NUM_BINDNAMES (sizeof bindnames / sizeof bindnames[0]) @@ -803,11 +803,20 @@ static menuframework_s s_joy_menu; static menuaction_s s_keys_actions[NUM_BINDNAMES]; static void -M_UnbindCommand(char *command) +M_UnbindCommand(char *command, int scope) { int j; + int begin = 0, end = K_LAST; + switch (scope) + { + case KEYS_KEYBOARD_MOUSE: + end = K_JOY_FIRST_REGULAR; + break; + case KEYS_CONTROLLER: + begin = K_JOY_FIRST_REGULAR; + } - for (j = 0; j < K_LAST; j++) + for (j = begin; j < end; j++) { char *b; b = keybindings[j]; @@ -825,15 +834,24 @@ M_UnbindCommand(char *command) } static void -M_FindKeysForCommand(char *command, int *twokeys) +M_FindKeysForCommand(char *command, int *twokeys, int scope) { int count; int j; + int begin = 0, end = K_LAST; + switch (scope) + { + case KEYS_KEYBOARD_MOUSE: + end = K_JOY_FIRST_REGULAR; + break; + case KEYS_CONTROLLER: + begin = K_JOY_FIRST_REGULAR; + } twokeys[0] = twokeys[1] = -1; count = 0; - for (j = 0; j < K_LAST; j++) + for (j = begin; j < end; j++) { char *b; b = keybindings[j]; @@ -879,7 +897,7 @@ DrawKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; float scale = SCR_GetMenuScale(); - M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys, KEYS_KEYBOARD_MOUSE); if (keys[0] == -1) { @@ -915,11 +933,11 @@ KeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; int keys[2]; - M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(bindnames[a->generic.localdata[0]][0], keys, KEYS_KEYBOARD_MOUSE); if (keys[1] != -1) { - M_UnbindCommand(bindnames[a->generic.localdata[0]][0]); + M_UnbindCommand(bindnames[a->generic.localdata[0]][0], KEYS_KEYBOARD_MOUSE); } menukeyitem_bind = true; @@ -967,7 +985,8 @@ Keys_MenuKey(int key) if (menukeyitem_bind) { - if ((key != K_ESCAPE) && (key != '`')) + // Any key/button except from the game controller and escape keys + if ((key != K_ESCAPE) && (key != '`') && (key < K_JOY_FIRST_REGULAR)) { char cmd[1024]; @@ -988,7 +1007,7 @@ Keys_MenuKey(int key) KeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - M_UnbindCommand(bindnames[item->generic.localdata[0]][0]); + M_UnbindCommand(bindnames[item->generic.localdata[0]][0], KEYS_KEYBOARD_MOUSE); return menu_out_sound; default: return Default_MenuKey(&s_keys_menu, key); @@ -1028,7 +1047,7 @@ MultiplayerDrawKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; float scale = SCR_GetMenuScale(); - M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys, KEYS_ALL); if (keys[0] == -1) { @@ -1064,11 +1083,11 @@ MultiplayerKeyBindingFunc(void *self) menuaction_s *a = (menuaction_s *)self; int keys[2]; - M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys); + M_FindKeysForCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], keys, KEYS_ALL); if (keys[1] != -1) { - M_UnbindCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0]); + M_UnbindCommand(multiplayer_key_bindnames[a->generic.localdata[0]][0], KEYS_ALL); } menukeyitem_bind = true; @@ -1116,7 +1135,8 @@ MultiplayerKeys_MenuKey(int key) if (menukeyitem_bind) { - if ((key != K_ESCAPE) && (key != '`')) + // Any key/button but the escape ones + if ((key != K_ESCAPE) && (key != '`') && (key != K_JOY_BACK)) { char cmd[1024]; @@ -1137,7 +1157,7 @@ MultiplayerKeys_MenuKey(int key) MultiplayerKeyBindingFunc(item); return menu_in_sound; case K_BACKSPACE: /* delete bindings */ - M_UnbindCommand(multiplayer_key_bindnames[item->generic.localdata[0]][0]); + M_UnbindCommand(multiplayer_key_bindnames[item->generic.localdata[0]][0], KEYS_ALL); return menu_out_sound; default: return Default_MenuKey(&s_multiplayer_keys_menu, key); @@ -1151,6 +1171,168 @@ M_Menu_Multiplayer_Keys_f(void) M_PushMenu(MultiplayerKeys_MenuDraw, MultiplayerKeys_MenuKey); } +/* + * GAME CONTROLLER ( GAMEPAD / JOYSTICK ) BUTTONS MENU + */ + +char *controller_bindnames[][2] = +{ + {"+attack", "attack"}, + {"+moveup", "up / jump"}, + {"+movedown", "down / crouch"}, + {"weapnext", "next weapon"}, + {"weapprev", "previous weapon"}, + {"cycleweap weapon_chaingun weapon_machinegun weapon_blaster", "long range: quickswitch 1"}, + {"cycleweap weapon_supershotgun weapon_shotgun", "close range: quickswitch 2"}, + {"cycleweap weapon_rocketlauncher weapon_grenadelauncher ammo_grenades", "explosives: quickswitch 3"}, + {"cycleweap weapon_bfg weapon_railgun weapon_hyperblaster", "special: quickswitch 4"}, + {"prefweap weapon_railgun weapon_hyperblaster weapon_chaingun weapon_supershotgun weapon_machinegun weapon_shotgun weapon_blaster", "best safe weapon"}, + {"prefweap weapon_bfg weapon_railgun weapon_rocketlauncher weapon_hyperblaster weapon_grenadelauncher weapon_chaingun ammo_grenades weapon_supershotgun", "best unsafe weapon"}, + {"centerview", "center view"}, + {"inven", "inventory"}, + {"invuse", "use item"}, + {"invdrop", "drop item"}, + {"invprev", "prev item"}, + {"invnext", "next item"}, + {"cmd help", "help computer"}, + {"+joyaltselector", "alt buttons modifier"} +}; +#define NUM_CONTROLLER_BINDNAMES (sizeof controller_bindnames / sizeof controller_bindnames[0]) + +static menuframework_s s_controller_buttons_menu; +static menuaction_s s_controller_buttons_actions[NUM_CONTROLLER_BINDNAMES]; + +static void +DrawControllerButtonBindingFunc(void *self) +{ + int keys[2]; + menuaction_s *a = (menuaction_s *)self; + float scale = SCR_GetMenuScale(); + + M_FindKeysForCommand(controller_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER); + + if (keys[0] == -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, "???"); + } + else + { + int x; + const char *name; + + name = Key_KeynumToString(keys[0]); + + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, name); + + x = strlen(name) * 8; + + if (keys[1] != -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + 24 * scale + (x * scale), + a->generic.y + a->generic.parent->y, "or"); + Menu_DrawString(a->generic.x + a->generic.parent->x + 48 * scale + (x * scale), + a->generic.y + a->generic.parent->y, + Key_KeynumToString(keys[1])); + } + } +} + +static void +ControllerButtonBindingFunc(void *self) +{ + menuaction_s *a = (menuaction_s *)self; + int keys[2]; + + M_FindKeysForCommand(controller_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER); + + if (keys[1] != -1) + { + M_UnbindCommand(controller_bindnames[a->generic.localdata[0]][0], KEYS_CONTROLLER); + } + + menukeyitem_bind = true; + + Menu_SetStatusBar(&s_controller_buttons_menu, "press a button for this action"); +} + +static void +ControllerButtons_MenuInit(void) +{ + int i; + + s_controller_buttons_menu.x = (int)(viddef.width * 0.50f); + s_controller_buttons_menu.nitems = 0; + s_controller_buttons_menu.cursordraw = KeyCursorDrawFunc; + + for (i = 0; i < NUM_CONTROLLER_BINDNAMES; i++) + { + s_controller_buttons_actions[i].generic.type = MTYPE_ACTION; + s_controller_buttons_actions[i].generic.flags = QMF_GRAYED; + s_controller_buttons_actions[i].generic.x = 0; + s_controller_buttons_actions[i].generic.y = (i * 9); + s_controller_buttons_actions[i].generic.ownerdraw = DrawControllerButtonBindingFunc; + s_controller_buttons_actions[i].generic.localdata[0] = i; + s_controller_buttons_actions[i].generic.name = controller_bindnames[s_controller_buttons_actions[i].generic.localdata[0]][1]; + + Menu_AddItem(&s_controller_buttons_menu, (void *)&s_controller_buttons_actions[i]); + } + + Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + Menu_Center(&s_controller_buttons_menu); +} + +static void +ControllerButtons_MenuDraw(void) +{ + Menu_AdjustCursor(&s_controller_buttons_menu, 1); + Menu_Draw(&s_controller_buttons_menu); +} + +static const char * +ControllerButtons_MenuKey(int key) +{ + menuaction_s *item = (menuaction_s *)Menu_ItemAtCursor(&s_controller_buttons_menu); + + if (menukeyitem_bind) + { + // Only controller buttons allowed + if (key >= K_JOY_FIRST_REGULAR && key != K_JOY_BACK) + { + char cmd[1024]; + + Com_sprintf(cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", + Key_KeynumToString(key), controller_bindnames[item->generic.localdata[0]][0]); + Cbuf_InsertText(cmd); + } + + Menu_SetStatusBar(&s_controller_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + menukeyitem_bind = false; + return menu_out_sound; + } + + key = Key_GetMenuKey(key); + switch (key) + { + case K_ENTER: + ControllerButtonBindingFunc(item); + return menu_in_sound; + case K_BACKSPACE: + M_UnbindCommand(controller_bindnames[item->generic.localdata[0]][0], KEYS_CONTROLLER); + return menu_out_sound; + default: + return Default_MenuKey(&s_controller_buttons_menu, key); + } +} + +static void +M_Menu_ControllerButtons_f(void) +{ + ControllerButtons_MenuInit(); + M_PushMenu(ControllerButtons_MenuDraw, ControllerButtons_MenuKey); +} + /* * JOY MENU */ @@ -1161,6 +1343,13 @@ static menuslider_s s_joy_forwardsensitivity_slider; static menuslider_s s_joy_sidesensitivity_slider; static menuslider_s s_joy_upsensitivity_slider; static menuslider_s s_joy_haptic_slider; +static menuaction_s s_joy_customize_buttons_action; + +static void +CustomizeControllerButtonsFunc(void *unused) +{ + M_Menu_ControllerButtons_f(); +} static void HapticMagnitudeFunc(void *unused) @@ -1300,6 +1489,16 @@ Joy_MenuInit(void) Menu_AddItem(&s_joy_menu, (void *)&s_joy_haptic_slider); } + y += 10; + + s_joy_customize_buttons_action.generic.type = MTYPE_ACTION; + s_joy_customize_buttons_action.generic.x = 0; + s_joy_customize_buttons_action.generic.y = y; + y += 10; + s_joy_customize_buttons_action.generic.name = "customize buttons"; + s_joy_customize_buttons_action.generic.callback = CustomizeControllerButtonsFunc; + Menu_AddItem(&s_joy_menu, (void *)&s_joy_customize_buttons_action); + Menu_Center(&s_joy_menu); } @@ -1669,7 +1868,7 @@ Options_MenuInit(void) s_options_customize_joy_action.generic.type = MTYPE_ACTION; s_options_customize_joy_action.generic.x = 0; s_options_customize_joy_action.generic.y = 130; - s_options_customize_joy_action.generic.name = "customize joystick"; + s_options_customize_joy_action.generic.name = "customize gamepad"; s_options_customize_joy_action.generic.callback = CustomizeJoyFunc; s_options_customize_options_action.generic.type = MTYPE_ACTION; @@ -5041,6 +5240,7 @@ M_Init(void) Cmd_AddCommand("menu_options", M_Menu_Options_f); Cmd_AddCommand("menu_keys", M_Menu_Keys_f); Cmd_AddCommand("menu_joy", M_Menu_Joy_f); + Cmd_AddCommand("menu_buttons", M_Menu_ControllerButtons_f); Cmd_AddCommand("menu_quit", M_Menu_Quit_f); /* initialize the server address book cvars (adr0, adr1, ...) From 20542420ef243634a8faaaa5112db1155840bcc5 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sun, 8 May 2022 00:24:52 -0400 Subject: [PATCH 4/5] New controller alt buttons menu option Allows to bind buttons that only will work with "+joyaltselector" active. The definition of another "scope" of keys was needed, to identify ALT bindings. --- src/client/menu/header/qmenu.h | 3 +- src/client/menu/menu.c | 189 +++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) diff --git a/src/client/menu/header/qmenu.h b/src/client/menu/header/qmenu.h index a8f3cde6..2070fa56 100644 --- a/src/client/menu/header/qmenu.h +++ b/src/client/menu/header/qmenu.h @@ -46,7 +46,8 @@ enum { KEYS_ALL = 0, KEYS_KEYBOARD_MOUSE, - KEYS_CONTROLLER + KEYS_CONTROLLER, + KEYS_CONTROLLER_ALT }; typedef struct _tag_menuframework diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c index bf996d4b..afecd72e 100644 --- a/src/client/menu/menu.c +++ b/src/client/menu/menu.c @@ -65,6 +65,7 @@ static void M_Menu_Options_f(void); static void M_Menu_Keys_f(void); static void M_Menu_Joy_f(void); static void M_Menu_ControllerButtons_f(void); +static void M_Menu_ControllerAltButtons_f(void); static void M_Menu_Quit_f(void); void M_Menu_Credits(void); @@ -814,6 +815,10 @@ M_UnbindCommand(char *command, int scope) break; case KEYS_CONTROLLER: begin = K_JOY_FIRST_REGULAR; + end = K_JOY_LAST_REGULAR + 1; + break; + case KEYS_CONTROLLER_ALT: + begin = K_JOY_FIRST_REGULAR_ALT; } for (j = begin; j < end; j++) @@ -846,6 +851,10 @@ M_FindKeysForCommand(char *command, int *twokeys, int scope) break; case KEYS_CONTROLLER: begin = K_JOY_FIRST_REGULAR; + end = K_JOY_LAST_REGULAR + 1; + break; + case KEYS_CONTROLLER_ALT: + begin = K_JOY_FIRST_REGULAR_ALT; } twokeys[0] = twokeys[1] = -1; @@ -1333,6 +1342,170 @@ M_Menu_ControllerButtons_f(void) M_PushMenu(ControllerButtons_MenuDraw, ControllerButtons_MenuKey); } +/* + * GAME CONTROLLER ALTERNATE BUTTONS MENU + */ + +char *controller_alt_bindnames[][2] = +{ + {"weapnext", "next weapon"}, + {"weapprev", "previous weapon"}, + {"cycleweap weapon_chaingun weapon_machinegun weapon_blaster", "long range: quickswitch 1"}, + {"cycleweap weapon_supershotgun weapon_shotgun", "close range: quickswitch 2"}, + {"cycleweap weapon_rocketlauncher weapon_grenadelauncher ammo_grenades", "explosives: quickswitch 3"}, + {"cycleweap weapon_bfg weapon_railgun weapon_hyperblaster", "special: quickswitch 4"}, + {"prefweap weapon_railgun weapon_hyperblaster weapon_chaingun weapon_supershotgun weapon_machinegun weapon_shotgun weapon_blaster", "best safe weapon"}, + {"prefweap weapon_bfg weapon_railgun weapon_rocketlauncher weapon_hyperblaster weapon_grenadelauncher weapon_chaingun ammo_grenades weapon_supershotgun", "best unsafe weapon"}, + {"centerview", "center view"}, + {"inven", "inventory"}, + {"invuse", "use item"}, + {"invdrop", "drop item"}, + {"invprev", "prev item"}, + {"invnext", "next item"}, + {"use invulnerability", "use invulnerability"}, + {"use rebreather", "use rebreather"}, + {"use environment suit", "use environment suit"}, + {"use power shield", "use power shield"}, + {"use quad damage", "use quad damage"}, + {"cmd help", "help computer"} +}; +#define NUM_CONTROLLER_ALT_BINDNAMES (sizeof controller_alt_bindnames / sizeof controller_alt_bindnames[0]) + +static menuframework_s s_controller_alt_buttons_menu; +static menuaction_s s_controller_alt_buttons_actions[NUM_CONTROLLER_ALT_BINDNAMES]; + +static void +DrawControllerAltButtonBindingFunc(void *self) +{ + int keys[2]; + menuaction_s *a = (menuaction_s *)self; + float scale = SCR_GetMenuScale(); + + M_FindKeysForCommand(controller_alt_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER_ALT); + + if (keys[0] == -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, "???"); + } + else + { + int x; + const char *name; + + name = Key_KeynumToString(keys[0]); + + Menu_DrawString(a->generic.x + a->generic.parent->x + RCOLUMN_OFFSET * scale, + a->generic.y + a->generic.parent->y, name); + + x = strlen(name) * 8; + + if (keys[1] != -1) + { + Menu_DrawString(a->generic.x + a->generic.parent->x + 24 * scale + (x * scale), + a->generic.y + a->generic.parent->y, "or"); + Menu_DrawString(a->generic.x + a->generic.parent->x + 48 * scale + (x * scale), + a->generic.y + a->generic.parent->y, + Key_KeynumToString(keys[1])); + } + } +} + +static void +ControllerAltButtonBindingFunc(void *self) +{ + menuaction_s *a = (menuaction_s *)self; + int keys[2]; + + M_FindKeysForCommand(controller_alt_bindnames[a->generic.localdata[0]][0], keys, KEYS_CONTROLLER_ALT); + + if (keys[1] != -1) + { + M_UnbindCommand(controller_alt_bindnames[a->generic.localdata[0]][0], KEYS_CONTROLLER_ALT); + } + + menukeyitem_bind = true; + + Menu_SetStatusBar(&s_controller_alt_buttons_menu, "press a button for this action"); +} + +static void +ControllerAltButtons_MenuInit(void) +{ + int i; + + s_controller_alt_buttons_menu.x = (int)(viddef.width * 0.50f); + s_controller_alt_buttons_menu.nitems = 0; + s_controller_alt_buttons_menu.cursordraw = KeyCursorDrawFunc; + + for (i = 0; i < NUM_CONTROLLER_ALT_BINDNAMES; i++) + { + s_controller_alt_buttons_actions[i].generic.type = MTYPE_ACTION; + s_controller_alt_buttons_actions[i].generic.flags = QMF_GRAYED; + s_controller_alt_buttons_actions[i].generic.x = 0; + s_controller_alt_buttons_actions[i].generic.y = (i * 9); + s_controller_alt_buttons_actions[i].generic.ownerdraw = DrawControllerAltButtonBindingFunc; + s_controller_alt_buttons_actions[i].generic.localdata[0] = i; + s_controller_alt_buttons_actions[i].generic.name = controller_alt_bindnames[s_controller_alt_buttons_actions[i].generic.localdata[0]][1]; + + Menu_AddItem(&s_controller_alt_buttons_menu, (void *)&s_controller_alt_buttons_actions[i]); + } + + Menu_SetStatusBar(&s_controller_alt_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + Menu_Center(&s_controller_alt_buttons_menu); +} + +static void +ControllerAltButtons_MenuDraw(void) +{ + Menu_AdjustCursor(&s_controller_alt_buttons_menu, 1); + Menu_Draw(&s_controller_alt_buttons_menu); +} + +static const char * +ControllerAltButtons_MenuKey(int key) +{ + menuaction_s *item = (menuaction_s *)Menu_ItemAtCursor(&s_controller_alt_buttons_menu); + + if (menukeyitem_bind) + { + // Only controller buttons allowed, different from the alt buttons modifier + if (key >= K_JOY_FIRST_REGULAR && key != K_JOY_BACK && (keybindings[key] == NULL || strcmp(keybindings[key], "+joyaltselector") != 0)) + { + char cmd[1024]; + key = key + (K_JOY_FIRST_REGULAR_ALT - K_JOY_FIRST_REGULAR); // change input to its ALT mode + + Com_sprintf(cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", + Key_KeynumToString(key), controller_alt_bindnames[item->generic.localdata[0]][0]); + Cbuf_InsertText(cmd); + } + + Menu_SetStatusBar(&s_controller_alt_buttons_menu, "BTN_A assigns, BTN_Y clears, BTN_B exits"); + menukeyitem_bind = false; + return menu_out_sound; + } + + key = Key_GetMenuKey(key); + switch (key) + { + case K_ENTER: + ControllerAltButtonBindingFunc(item); + return menu_in_sound; + case K_BACKSPACE: + M_UnbindCommand(controller_alt_bindnames[item->generic.localdata[0]][0], KEYS_CONTROLLER_ALT); + return menu_out_sound; + default: + return Default_MenuKey(&s_controller_alt_buttons_menu, key); + } +} + +static void +M_Menu_ControllerAltButtons_f(void) +{ + ControllerAltButtons_MenuInit(); + M_PushMenu(ControllerAltButtons_MenuDraw, ControllerAltButtons_MenuKey); +} + /* * JOY MENU */ @@ -1344,6 +1517,7 @@ static menuslider_s s_joy_sidesensitivity_slider; static menuslider_s s_joy_upsensitivity_slider; static menuslider_s s_joy_haptic_slider; static menuaction_s s_joy_customize_buttons_action; +static menuaction_s s_joy_customize_alt_buttons_action; static void CustomizeControllerButtonsFunc(void *unused) @@ -1351,6 +1525,12 @@ CustomizeControllerButtonsFunc(void *unused) M_Menu_ControllerButtons_f(); } +static void +CustomizeControllerAltButtonsFunc(void *unused) +{ + M_Menu_ControllerAltButtons_f(); +} + static void HapticMagnitudeFunc(void *unused) { @@ -1499,6 +1679,14 @@ Joy_MenuInit(void) s_joy_customize_buttons_action.generic.callback = CustomizeControllerButtonsFunc; Menu_AddItem(&s_joy_menu, (void *)&s_joy_customize_buttons_action); + s_joy_customize_alt_buttons_action.generic.type = MTYPE_ACTION; + s_joy_customize_alt_buttons_action.generic.x = 0; + s_joy_customize_alt_buttons_action.generic.y = y; + y += 10; + s_joy_customize_alt_buttons_action.generic.name = "customize alt buttons"; + s_joy_customize_alt_buttons_action.generic.callback = CustomizeControllerAltButtonsFunc; + Menu_AddItem(&s_joy_menu, (void *)&s_joy_customize_alt_buttons_action); + Menu_Center(&s_joy_menu); } @@ -5241,6 +5429,7 @@ M_Init(void) Cmd_AddCommand("menu_keys", M_Menu_Keys_f); Cmd_AddCommand("menu_joy", M_Menu_Joy_f); Cmd_AddCommand("menu_buttons", M_Menu_ControllerButtons_f); + Cmd_AddCommand("menu_altbuttons", M_Menu_ControllerAltButtons_f); Cmd_AddCommand("menu_quit", M_Menu_Quit_f); /* initialize the server address book cvars (adr0, adr1, ...) From 422715cee24bb66e83fd9d173d069438fd464487 Mon Sep 17 00:00:00 2001 From: Jaime Moreira Date: Sun, 8 May 2022 01:05:07 -0400 Subject: [PATCH 5/5] Hot plugging / hot swapping of game controller(s) May be unreliable on some devices, e.g. Nintendo Switch Pro Controller. Works great on others, e.g. DualShock 4. Delay added to gamepad initialization on hotplug, gives time to the OS to recognize the device. --- src/client/input/sdl.c | 76 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/src/client/input/sdl.c b/src/client/input/sdl.c index 15362369..c9eca159 100644 --- a/src/client/input/sdl.c +++ b/src/client/input/sdl.c @@ -130,6 +130,10 @@ static cvar_t *joy_axis_triggerright_threshold; // Joystick haptic static cvar_t *joy_haptic_magnitude; +// Support for hot plugging of game controller +static qboolean first_init = true; +static int init_delay = 30; + /* ------------------------------------------------------------------ */ /* @@ -458,6 +462,9 @@ IN_TranslateGamepadBtnToQ2Key(int btn) return 0; } +static void IN_Controller_Init(qboolean notify_user); +static void IN_Controller_Shutdown(qboolean notify_user); + /* ------------------------------------------------------------------ */ /* @@ -634,7 +641,6 @@ IN_Update(void) if(key != 0) { Key_Event(key, down, true); - } break; @@ -772,6 +778,25 @@ IN_Update(void) break; } + case SDL_CONTROLLERDEVICEREMOVED: + if (!controller) + { + break; + } + if (event.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) { + IN_Controller_Shutdown(true); + IN_Controller_Init(false); + } + break; + + case SDL_JOYDEVICEADDED: + if (!controller) + { + // This should be lower, but some controllers just don't want to get detected by the OS + init_delay = 100; + } + break; + case SDL_QUIT: Com_Quit(); break; @@ -796,6 +821,26 @@ IN_Update(void) // We need to save the frame time so other subsystems // know the exact time of the last input events. sys_frame_time = Sys_Milliseconds(); + + // Hot plugging delay handling, to not be "overwhelmed" because some controllers + // present themselves as two different devices, triggering SDL_JOYDEVICEADDED + // too many times. They could trigger it even at game initialization. + if (init_delay) + { + init_delay--; + if (!init_delay) + { + if (!first_init) + { + IN_Controller_Shutdown(false); + IN_Controller_Init(true); + } + else + { + first_init = false; + } + } + } } /* @@ -1192,7 +1237,7 @@ Haptic_Feedback(char *name, int effect_volume, int effect_duration, * Game Controller */ static void -IN_Controller_Init(void) +IN_Controller_Init(qboolean notify_user) { cvar_t *in_sdlbackbutton; int nummappings; @@ -1216,6 +1261,11 @@ IN_Controller_Init(void) } } + if (notify_user) + { + Com_Printf("- Game Controller init attempt -\n"); + } + if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC)) { if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == -1) @@ -1255,8 +1305,19 @@ IN_Controller_Init(void) { joystick = SDL_JoystickOpen(i); const char* joystick_name = SDL_JoystickName(joystick); + const int name_len = strlen(joystick_name); Com_Printf ("The name of the joystick is '%s'\n", joystick_name); + + // Ugly hack to detect IMU-only devices - works for Switch Pro Controller at least + if (name_len > 4 && !strncmp(joystick_name + name_len - 4, " IMU", 4)) + { + Com_Printf ("Skipping IMU device.\n"); + SDL_JoystickClose(joystick); + joystick = NULL; + continue; + } + Com_Printf ("Number of Axes: %d\n", SDL_JoystickNumAxes(joystick)); Com_Printf ("Number of Buttons: %d\n", SDL_JoystickNumButtons(joystick)); Com_Printf ("Number of Balls: %d\n", SDL_JoystickNumBalls(joystick)); @@ -1373,7 +1434,7 @@ IN_Init(void) SDL_StartTextInput(); - IN_Controller_Init(); + IN_Controller_Init(false); Com_Printf("------------------------------------\n\n"); } @@ -1394,8 +1455,13 @@ IN_Haptic_Shutdown(void) } static void -IN_Controller_Shutdown(void) +IN_Controller_Shutdown(qboolean notify_user) { + if (notify_user) + { + Com_Printf("- Game Controller disconnected -\n"); + } + IN_Haptic_Shutdown(); if (controller) @@ -1414,7 +1480,7 @@ IN_Shutdown(void) Com_Printf("Shutting down input.\n"); - IN_Controller_Shutdown(); + IN_Controller_Shutdown(false); const Uint32 subsystems = SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC; if (SDL_WasInit(subsystems) == subsystems)