From dfe0a3d4e6b8fbf4d2d03fed9ccb8c32686e10fa Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Mon, 13 Apr 2026 17:11:27 +0200 Subject: [PATCH] fix: corregit el tractament de mandos connectats --- data/lang/ba_BA.txt | 8 ++- data/lang/en_UK.txt | 8 ++- data/lang/es_ES.txt | 8 ++- source/director.cpp | 19 +++++++ source/input.cpp | 124 ++++++++++++++++++++++++++++++++++++++------ source/input.h | 17 +++++- source/title.cpp | 5 +- 7 files changed, 167 insertions(+), 22 deletions(-) diff --git a/data/lang/ba_BA.txt b/data/lang/ba_BA.txt index 7bc08f7..921cacd 100644 --- a/data/lang/ba_BA.txt +++ b/data/lang/ba_BA.txt @@ -278,4 +278,10 @@ DEIXA BUIT PER A MODE FORA DE LINEA ## 93 - MENU OPCIONES -TAULER DE PUNTS \ No newline at end of file +TAULER DE PUNTS + +## 94 - NOTIFICACIO COMANDAMENT +CONNECTAT + +## 95 - NOTIFICACIO COMANDAMENT +DESCONNECTAT \ No newline at end of file diff --git a/data/lang/en_UK.txt b/data/lang/en_UK.txt index 52da48b..9deee45 100644 --- a/data/lang/en_UK.txt +++ b/data/lang/en_UK.txt @@ -278,4 +278,10 @@ LEAVE BLANK FOR OFFLINE MODE ## 93 - MENU OPCIONES -HISCORE TABLE \ No newline at end of file +HISCORE TABLE + +## 94 - GAMEPAD NOTIFICATION +CONNECTED + +## 95 - GAMEPAD NOTIFICATION +DISCONNECTED \ No newline at end of file diff --git a/data/lang/es_ES.txt b/data/lang/es_ES.txt index 0bd763e..cc751c9 100644 --- a/data/lang/es_ES.txt +++ b/data/lang/es_ES.txt @@ -278,4 +278,10 @@ DEJA EN BLANCO PARA MODO SIN CONEXION ## 93 - MENU OPCIONES -TABLA DE PUNTUACIONES \ No newline at end of file +TABLA DE PUNTUACIONES + +## 94 - NOTIFICACION MANDO +CONECTADO + +## 95 - NOTIFICACION MANDO +DESCONECTADO \ No newline at end of file diff --git a/source/director.cpp b/source/director.cpp index b622b65..4a55fd6 100644 --- a/source/director.cpp +++ b/source/director.cpp @@ -695,6 +695,25 @@ SDL_AppResult Director::handleEvent(SDL_Event *event) { } #endif + // Hot-plug de mandos + if (event->type == SDL_EVENT_GAMEPAD_ADDED) { + std::string name; + if (input->handleGamepadAdded(event->gdevice.which, name)) { + screen->notify(name + " " + lang->getText(94), + color_t{0x40, 0xFF, 0x40}, + color_t{0, 0, 0}, + 2500); + } + } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) { + std::string name; + if (input->handleGamepadRemoved(event->gdevice.which, name)) { + screen->notify(name + " " + lang->getText(95), + color_t{0xFF, 0x50, 0x50}, + color_t{0, 0, 0}, + 2500); + } + } + // Gestiona la visibilidad del cursor según el movimiento del ratón Mouse::handleEvent(*event, options->videoMode != 0); diff --git a/source/input.cpp b/source/input.cpp index 1743d85..8fc84c3 100644 --- a/source/input.cpp +++ b/source/input.cpp @@ -20,10 +20,24 @@ Input::Input(std::string file) { gcb.active = false; gameControllerBindings.resize(input_number_of_inputs, gcb); + numGamepads = 0; verbose = true; enabled = true; } +// Destructor +Input::~Input() { + for (auto *pad : connectedControllers) { + if (pad != nullptr) { + SDL_CloseGamepad(pad); + } + } + connectedControllers.clear(); + connectedControllerIds.clear(); + controllerNames.clear(); + numGamepads = 0; +} + // Actualiza el estado del objeto void Input::update() { if (disabledUntil == d_keyPressed && !checkAnyInput()) { @@ -82,7 +96,7 @@ bool Input::checkInput(Uint8 input, bool repeat, int device, int index) { } } - if (gameControllerFound()) + if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size()) if ((device == INPUT_USE_GAMECONTROLLER) || (device == INPUT_USE_ANY)) { if (repeat) { if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[input].button)) { @@ -128,7 +142,7 @@ bool Input::checkAnyInput(int device, int index) { } } - if (gameControllerFound()) { + if (gameControllerFound() && index >= 0 && index < (int)connectedControllers.size()) { if (device == INPUT_USE_GAMECONTROLLER || device == INPUT_USE_ANY) { for (int i = 0; i < (int)gameControllerBindings.size(); ++i) { if (SDL_GetGamepadButton(connectedControllers[index], gameControllerBindings[i].button)) { @@ -141,8 +155,30 @@ bool Input::checkAnyInput(int device, int index) { return false; } -// Busca si hay un mando conectado +// Construye el nombre visible de un mando +std::string Input::buildControllerName(SDL_Gamepad *pad, int padIndex) { + const char *padName = SDL_GetGamepadName(pad); + std::string name = padName ? padName : "Unknown"; + if (name.size() > 25) { + name.resize(25); + } + return name + " #" + std::to_string(padIndex); +} + +// Busca si hay un mando conectado. Cierra y limpia el estado previo para +// que la función sea idempotente si se invoca más de una vez. bool Input::discoverGameController() { + // Cierra los mandos ya abiertos y limpia los vectores paralelos + for (auto *pad : connectedControllers) { + if (pad != nullptr) { + SDL_CloseGamepad(pad); + } + } + connectedControllers.clear(); + connectedControllerIds.clear(); + controllerNames.clear(); + numGamepads = 0; + bool found = false; if (SDL_WasInit(SDL_INIT_GAMEPAD) != SDL_INIT_GAMEPAD) { @@ -157,42 +193,38 @@ bool Input::discoverGameController() { int nJoysticks = 0; SDL_JoystickID *joysticks = SDL_GetJoysticks(&nJoysticks); - numGamepads = 0; if (joysticks) { - // Cuenta el numero de mandos + int gamepadCount = 0; for (int i = 0; i < nJoysticks; ++i) { if (SDL_IsGamepad(joysticks[i])) { - numGamepads++; + gamepadCount++; } } if (verbose) { std::cout << "\nChecking for game controllers...\n"; - std::cout << nJoysticks << " joysticks found, " << numGamepads << " are gamepads\n"; + std::cout << nJoysticks << " joysticks found, " << gamepadCount << " are gamepads\n"; } - if (numGamepads > 0) { + if (gamepadCount > 0) { found = true; int padIndex = 0; for (int i = 0; i < nJoysticks; i++) { if (!SDL_IsGamepad(joysticks[i])) continue; - // Abre el mando y lo añade a la lista SDL_Gamepad *pad = SDL_OpenGamepad(joysticks[i]); if (pad != nullptr) { + const std::string name = buildControllerName(pad, padIndex); connectedControllers.push_back(pad); - const std::string separator(" #"); - const char *padName = SDL_GetGamepadName(pad); - std::string name = padName ? padName : "Unknown"; - name.resize(25); - name = name + separator + std::to_string(padIndex); + connectedControllerIds.push_back(joysticks[i]); + controllerNames.push_back(name); + numGamepads++; + padIndex++; if (verbose) { std::cout << name << std::endl; } - controllerNames.push_back(name); - padIndex++; } else { if (verbose) { std::cout << "SDL_GetError() = " << SDL_GetError() << std::endl; @@ -209,6 +241,66 @@ bool Input::discoverGameController() { return found; } +// Procesa un evento SDL_EVENT_GAMEPAD_ADDED +bool Input::handleGamepadAdded(SDL_JoystickID jid, std::string &outName) { + if (!SDL_IsGamepad(jid)) { + return false; + } + + // Si el mando ya está registrado no hace nada (ej. evento retroactivo tras el scan inicial) + for (SDL_JoystickID existing : connectedControllerIds) { + if (existing == jid) { + return false; + } + } + + SDL_Gamepad *pad = SDL_OpenGamepad(jid); + if (pad == nullptr) { + if (verbose) { + std::cout << "Failed to open gamepad " << jid << ": " << SDL_GetError() << std::endl; + } + return false; + } + + const int padIndex = (int)connectedControllers.size(); + const std::string name = buildControllerName(pad, padIndex); + connectedControllers.push_back(pad); + connectedControllerIds.push_back(jid); + controllerNames.push_back(name); + numGamepads++; + + if (verbose) { + std::cout << "Gamepad connected: " << name << std::endl; + } + + outName = name; + return true; +} + +// Procesa un evento SDL_EVENT_GAMEPAD_REMOVED +bool Input::handleGamepadRemoved(SDL_JoystickID jid, std::string &outName) { + for (size_t i = 0; i < connectedControllerIds.size(); ++i) { + if (connectedControllerIds[i] != jid) continue; + + outName = controllerNames[i]; + if (connectedControllers[i] != nullptr) { + SDL_CloseGamepad(connectedControllers[i]); + } + connectedControllers.erase(connectedControllers.begin() + i); + connectedControllerIds.erase(connectedControllerIds.begin() + i); + controllerNames.erase(controllerNames.begin() + i); + numGamepads--; + if (numGamepads < 0) numGamepads = 0; + + if (verbose) { + std::cout << "Gamepad disconnected: " << outName << std::endl; + } + + return true; + } + return false; +} + // Comprueba si hay algun mando conectado bool Input::gameControllerFound() { if (numGamepads > 0) { diff --git a/source/input.h b/source/input.h index 1dfe338..9d635ef 100644 --- a/source/input.h +++ b/source/input.h @@ -57,7 +57,8 @@ class Input { }; // Objetos y punteros - std::vector connectedControllers; // Vector con todos los mandos conectados + std::vector connectedControllers; // Vector con todos los mandos conectados + std::vector connectedControllerIds; // Instance IDs paralelos para mapear eventos // Variables std::vector keyBindings; // Vector con las teclas asociadas a los inputs predefinidos @@ -69,10 +70,16 @@ class Input { i_disable_e disabledUntil; // Tiempo que esta deshabilitado bool enabled; // Indica si está habilitado + // Construye el nombre visible de un mando (name truncado + sufijo #N) + std::string buildControllerName(SDL_Gamepad *pad, int padIndex); + public: // Constructor Input(std::string file); + // Destructor + ~Input(); + // Actualiza el estado del objeto void update(); @@ -91,6 +98,14 @@ class Input { // Busca si hay un mando conectado bool discoverGameController(); + // Procesa un evento SDL_EVENT_GAMEPAD_ADDED. Devuelve true si el mando se ha añadido + // (no estaba ya registrado) y escribe el nombre visible en outName. + bool handleGamepadAdded(SDL_JoystickID jid, std::string &outName); + + // Procesa un evento SDL_EVENT_GAMEPAD_REMOVED. Devuelve true si se ha encontrado y + // eliminado, y escribe el nombre visible en outName. + bool handleGamepadRemoved(SDL_JoystickID jid, std::string &outName); + // Comprueba si hay algun mando conectado bool gameControllerFound(); diff --git a/source/title.cpp b/source/title.cpp index 39dee16..827b5e2 100644 --- a/source/title.cpp +++ b/source/title.cpp @@ -1097,12 +1097,13 @@ void Title::createTiledBackground() { delete tile; } -// Comprueba cuantos mandos hay conectados para gestionar el menu de opciones +// Comprueba cuantos mandos hay conectados para gestionar el menu de opciones. +// El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED, +// así que aquí solo leemos la lista actual sin reescanear. void Title::checkInputDevices() { if (options->console) { std::cout << "Filling devices for options menu..." << std::endl; } - input->discoverGameController(); const int numControllers = input->getNumControllers(); availableInputDevices.clear(); input_t temp;