From c694781f381f9146c16bf1704d195995a927f451 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Tue, 14 Apr 2026 17:09:09 +0200 Subject: [PATCH] =?UTF-8?q?correccions=20en=20la=20detecci=C3=B3=20de=20ma?= =?UTF-8?q?ndos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/input/input.cpp | 40 ++++++++++++++---------- source/core/input/input.hpp | 1 - source/core/rendering/screen.cpp | 22 ++++++------- source/core/system/director.cpp | 13 ++++++-- source/core/system/global_events.cpp | 25 ++++++++++++--- source/core/system/global_events.hpp | 6 ++++ source/game/options.cpp | 46 ++++++++++++++++++++++++++-- source/game/options.hpp | 4 +++ 8 files changed, 121 insertions(+), 36 deletions(-) diff --git a/source/core/input/input.cpp b/source/core/input/input.cpp index 3e28011..1e7b03f 100644 --- a/source/core/input/input.cpp +++ b/source/core/input/input.cpp @@ -303,20 +303,6 @@ void Input::addGamepadMappingsFromFile() { } } -void Input::discoverGamepads() { - // Enumera los gamepads ya conectados sin drenar la cola de eventos de SDL - // (necesario con SDL_MAIN_USE_CALLBACKS, que entrega los eventos por SDL_AppEvent). - int count = 0; - SDL_JoystickID* joysticks = SDL_GetGamepads(&count); - if (joysticks == nullptr) { - return; - } - for (int i = 0; i < count; ++i) { - addGamepad(joysticks[i]); - } - SDL_free(joysticks); -} - void Input::initSDLGamePad() { if (SDL_WasInit(SDL_INIT_GAMEPAD) != 1) { if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { @@ -324,7 +310,9 @@ void Input::initSDLGamePad() { } else { addGamepadMappingsFromFile(); loadGamepadConfigs(); - discoverGamepads(); + // Los mandos ya conectados llegan como SDL_EVENT_GAMEPAD_ADDED en el + // primer pase del pump de eventos (antes del primer SDL_AppIterate), + // por lo que no hace falta enumerarlos aquí a mano. } } } @@ -444,6 +432,27 @@ void Input::applyGamepadConfig(std::shared_ptr gamepad) { return config.path == gamepad->path; }); + // Fallback por nombre: si el mismo dispositivo se enchufa a otro puerto, su + // path cambia pero el nombre suele mantenerse. Recuperamos su configuración + // y actualizamos el path guardado al actual. Solo se acepta un match cuyo + // path guardado NO corresponda a otro mando ya conectado, para no arruinar + // setups con varios mandos idénticos en puertos distintos (cabinet arcade). + if (config_it == gamepad_configs_.end() && !gamepad->name.empty()) { + auto is_path_active = [this](const std::string& query_path) -> bool { + return std::ranges::any_of(gamepads_, [&query_path](const std::shared_ptr& g) -> bool { + return g && g->path == query_path; + }); + }; + config_it = std::ranges::find_if(gamepad_configs_, [&gamepad, &is_path_active](const GamepadConfig& config) -> bool { + return config.name == gamepad->name && !is_path_active(config.path); + }); + if (config_it != gamepad_configs_.end()) { + std::cout << "Gamepad '" << gamepad->name << "' found by name, refreshing path to: " << gamepad->path << '\n'; + config_it->path = gamepad->path; + saveGamepadConfigs(); + } + } + if (config_it != gamepad_configs_.end()) { // Se encontró una configuración específica para este puerto/dispositivo. La aplicamos. std::cout << "Applying custom config for gamepad at path: " << gamepad->path << '\n'; @@ -453,7 +462,6 @@ void Input::applyGamepadConfig(std::shared_ptr gamepad) { } } } - // Opcional: Podrías añadir un fallback para buscar por nombre si no se encuentra por ruta. } void Input::saveGamepadConfigFromGamepad(std::shared_ptr gamepad) { diff --git a/source/core/input/input.hpp b/source/core/input/input.hpp index be54356..fcac883 100644 --- a/source/core/input/input.hpp +++ b/source/core/input/input.hpp @@ -206,7 +206,6 @@ class Input { auto addGamepad(int device_index) -> std::string; auto removeGamepad(SDL_JoystickID id) -> std::string; void addGamepadMappingsFromFile(); - void discoverGamepads(); // --- Métodos para integración con GamepadConfigManager --- void loadGamepadConfigs(); diff --git a/source/core/rendering/screen.cpp b/source/core/rendering/screen.cpp index 51b1497..711da15 100644 --- a/source/core/rendering/screen.cpp +++ b/source/core/rendering/screen.cpp @@ -9,20 +9,20 @@ #include // Para basic_string, operator+, char_traits, to_string, string #include // Para vector -#include "core/input/mouse.hpp" // Para updateCursorVisibility +#include "core/input/mouse.hpp" // Para updateCursorVisibility #ifndef NO_SHADERS #include "core/rendering/sdl3gpu/sdl3gpu_shader.hpp" // Para SDL3GPUShader #endif -#include "core/rendering/text.hpp" // Para Text -#include "core/rendering/texture.hpp" // Para Texture -#include "core/resources/asset.hpp" // Para Asset -#include "core/resources/resource.hpp" // Para Resource -#include "core/system/director.hpp" // Para Director::debug_config -#include "game/options.hpp" // Para Video, video, Window, window -#include "game/ui/notifier.hpp" // Para Notifier -#include "game/ui/service_menu.hpp" // Para ServiceMenu -#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug -#include "utils/utils.hpp" // Para toLower +#include "core/rendering/text.hpp" // Para Text +#include "core/rendering/texture.hpp" // Para Texture +#include "core/resources/asset.hpp" // Para Asset +#include "core/resources/resource.hpp" // Para Resource +#include "core/system/director.hpp" // Para Director::debug_config +#include "game/options.hpp" // Para Video, video, Window, window +#include "game/ui/notifier.hpp" // Para Notifier +#include "game/ui/service_menu.hpp" // Para ServiceMenu +#include "utils/param.hpp" // Para Param, param, ParamGame, ParamDebug +#include "utils/utils.hpp" // Para toLower // Singleton Screen* Screen::instance = nullptr; diff --git a/source/core/system/director.cpp b/source/core/system/director.cpp index 072b1d4..e724a9e 100644 --- a/source/core/system/director.cpp +++ b/source/core/system/director.cpp @@ -125,8 +125,8 @@ void Director::init() { Options::video.fullscreen = true; Options::video.integer_scale = true; #endif - loadParams(); // Carga los parámetros del programa - loadScoreFile(); // Carga el archivo de puntuaciones + loadParams(); // Carga los parámetros del programa + loadScoreFile(); // Carga el archivo de puntuaciones // Inicialización de subsistemas principales Lang::setLanguage(Options::settings.language); // Carga el archivo de idioma @@ -435,6 +435,15 @@ auto Director::iterate() -> SDL_AppResult { return SDL_APP_SUCCESS; } + // En el primer frame, SDL ya ha drenado los SDL_EVENT_GAMEPAD_ADDED de los + // mandos conectados al iniciar. A partir de ahora los eventos sí son hotplug + // real y deben mostrar notificación. + static bool first_iterate = true; + if (first_iterate) { + first_iterate = false; + GlobalEvents::markStartupComplete(); + } + // Gestiona las transiciones entre secciones (destruye la anterior y construye la nueva) handleSectionTransition(); diff --git a/source/core/system/global_events.cpp b/source/core/system/global_events.cpp index 2305196..7ae28c3 100644 --- a/source/core/system/global_events.cpp +++ b/source/core/system/global_events.cpp @@ -17,12 +17,28 @@ #include "game/ui/service_menu.hpp" // Para ServiceMenu namespace GlobalEvents { + namespace { + // Durante el arranque se drenan los SDL_EVENT_GAMEPAD_ADDED de los mandos + // ya conectados. Esos eventos sí deben reasignar mandos a jugadores, pero + // no deben mostrar notificación: no son hotplug, son detección inicial. + bool startup_in_progress = true; + } // namespace + // Comprueba los eventos de Input y muestra notificaciones void handleInputEvents(const SDL_Event& event) { + if (event.type != SDL_EVENT_GAMEPAD_ADDED && event.type != SDL_EVENT_GAMEPAD_REMOVED) { + return; + } + static auto* input_ = Input::get(); auto message = input_->handleEvent(event); - if (message.empty()) { + // Reasignar siempre: tanto en arranque como en hotplug en caliente. + Options::gamepad_manager.assignAndLinkGamepads(); + Options::gamepad_manager.resyncGamepadsWithPlayers(); + ServiceMenu::get()->refresh(); + + if (startup_in_progress || message.empty()) { return; } @@ -35,10 +51,11 @@ namespace GlobalEvents { message.replace(pos, std::string(" DISCONNECTED").length(), " " + Lang::getText("[NOTIFICATIONS] DISCONNECTED")); } - Options::gamepad_manager.assignAndLinkGamepads(); - Options::gamepad_manager.resyncGamepadsWithPlayers(); Notifier::get()->show({message}); - ServiceMenu::get()->refresh(); + } + + void markStartupComplete() { + startup_in_progress = false; } // Comprueba los eventos que se pueden producir en cualquier sección del juego diff --git a/source/core/system/global_events.hpp b/source/core/system/global_events.hpp index 7ead972..1e00779 100644 --- a/source/core/system/global_events.hpp +++ b/source/core/system/global_events.hpp @@ -6,4 +6,10 @@ namespace GlobalEvents { // --- Funciones --- void handle(const SDL_Event& event); // Comprueba los eventos que se pueden producir en cualquier sección del juego + + // Marca el fin del arranque: a partir de aquí, los eventos de mando + // generan notificaciones en pantalla. Se debe llamar desde Director::iterate + // en el primer fotograma, después de que SDL haya drenado los + // SDL_EVENT_GAMEPAD_ADDED de los mandos ya conectados al iniciar. + void markStartupComplete(); } // namespace GlobalEvents \ No newline at end of file diff --git a/source/game/options.cpp b/source/game/options.cpp index bdc240c..739adf5 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -763,18 +763,30 @@ namespace Options { } // Asigna los mandos físicos basándose en la configuración actual. + // + // Tres pases en orden decreciente de prioridad: + // 1) Match por path exacto: resuelve el caso arcade (dos sticks idénticos + // enchufados siempre a los mismos puertos USB). + // 2) Match por nombre cuando el path no coincidió: recoloca un mando + // conocido que se ha reenchufado a otro puerto (ej. DualSense). En ese + // caso se refresca el path guardado al nuevo. + // 3) Reparto en orden de los mandos físicos sobrantes a los slots que + // sigan libres. void GamepadManager::assignAndLinkGamepads() { auto physical_gamepads = Input::get()->getGamepads(); std::array desired_paths; + std::array desired_names; for (size_t i = 0; i < MAX_PLAYERS; ++i) { desired_paths[i] = gamepads_[i].path; + desired_names[i] = gamepads_[i].name; gamepads_[i].instance = nullptr; } std::vector> assigned_instances; assignGamepadsByPath(desired_paths, physical_gamepads, assigned_instances); + assignGamepadsByName(desired_names, physical_gamepads, assigned_instances); assignRemainingGamepads(physical_gamepads, assigned_instances); clearUnassignedGamepadSlots(); } @@ -802,7 +814,37 @@ namespace Options { } } - // --- SEGUNDA PASADA: Asigna los mandos físicos restantes a los jugadores libres --- + // --- SEGUNDA PASADA: Match por nombre para slots cuyo path guardado no casó --- + // Si un slot tiene nombre guardado pero el path no ha coincidido (mando reenchufado + // a otro puerto, o path del sistema cambiado), lo enlazamos por nombre y + // refrescamos el path guardado al del dispositivo físico actual. + void GamepadManager::assignGamepadsByName( + const std::array& desired_names, + const std::vector>& physical_gamepads, // NOLINT(readability-named-parameter) + std::vector>& assigned_instances) { + for (size_t i = 0; i < MAX_PLAYERS; ++i) { + if (gamepads_[i].instance != nullptr) { + continue; + } + const std::string& desired_name = desired_names[i]; + if (desired_name.empty()) { + continue; + } + + for (const auto& physical_gamepad : physical_gamepads) { + if (physical_gamepad->name == desired_name && !isGamepadAssigned(physical_gamepad, assigned_instances)) { + gamepads_[i].instance = physical_gamepad; + gamepads_[i].name = physical_gamepad->name; + gamepads_[i].path = physical_gamepad->path; + + assigned_instances.push_back(physical_gamepad); + break; + } + } + } + } + + // --- TERCERA PASADA: Asigna los mandos físicos restantes a los jugadores libres --- void GamepadManager::assignRemainingGamepads( const std::vector>& physical_gamepads, // NOLINT(readability-named-parameter) std::vector>& assigned_instances) { @@ -824,7 +866,7 @@ namespace Options { } } - // --- TERCERA PASADA: Limpia la información "fantasma" de los slots no asignados --- + // --- LIMPIEZA FINAL: Limpia la información "fantasma" de los slots no asignados --- void GamepadManager::clearUnassignedGamepadSlots() { for (auto& gamepad_config : gamepads_) { if (gamepad_config.instance == nullptr) { diff --git a/source/game/options.hpp b/source/game/options.hpp index e65c115..d383ef2 100644 --- a/source/game/options.hpp +++ b/source/game/options.hpp @@ -293,6 +293,10 @@ namespace Options { const std::array& desired_paths, const std::vector>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls) std::vector>& assigned_instances); + void assignGamepadsByName( + const std::array& desired_names, + const std::vector>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls) + std::vector>& assigned_instances); void assignRemainingGamepads( const std::vector>& physical_gamepads, // NOLINT(readability-avoid-const-params-in-decls) std::vector>& assigned_instances);