#include "core/input/gamepad_manager.hpp" #include #include #include #include #include "core/locale/locale.hpp" #include "core/rendering/notification_presets.hpp" namespace { #ifdef __EMSCRIPTEN__ // A Emscripten, SDL 3.4+ no casa el GUID dels mandos web: Chrome/Android // no aporten Vendor/Product ID i el parser de SDL extreu dades brutes. // El W3C Gamepad API, però, garanteix un layout estàndard quan el mapping // reporta "standard", així que injectem un mapping SDL explícit amb eixe // layout per al GUID del joystick abans que SDL intenti obrir-lo com a // gamepad. Fora d'Emscripten és un no-op. void installWebStandardMapping(SDL_JoystickID jid) { SDL_GUID guid = SDL_GetJoystickGUIDForID(jid); char guid_str[33]; SDL_GUIDToString(guid, guid_str, sizeof(guid_str)); const char* name = SDL_GetJoystickNameForID(jid); if (name == nullptr || *name == '\0') { name = "Standard Gamepad"; } char mapping[512]; SDL_snprintf(mapping, sizeof(mapping), "%s,%s," "a:b0,b:b1,x:b2,y:b3," "leftshoulder:b4,rightshoulder:b5," "lefttrigger:b6,righttrigger:b7," "back:b8,start:b9," "leftstick:b10,rightstick:b11," "dpup:b12,dpdown:b13,dpleft:b14,dpright:b15," "guide:b16," "leftx:a0,lefty:a1,rightx:a2,righty:a3," "platform:Emscripten", guid_str, name); SDL_AddGamepadMapping(mapping); } #else void installWebStandardMapping(SDL_JoystickID /*jid*/) {} #endif } // namespace auto GamepadManager::get() -> GamepadManager& { static GamepadManager instance_; return instance_; } auto GamepadManager::prettyName(const char* raw) -> std::string { std::string name = (raw != nullptr && *raw != '\0') ? std::string(raw) : std::string("Gamepad"); const auto POS = name.find_first_of("(["); if (POS != std::string::npos) { name.erase(POS); } while (!name.empty() && name.back() == ' ') { name.pop_back(); } if (name.empty()) { name = "Gamepad"; } return name; } auto GamepadManager::uniqueName(const std::string& base) const -> std::string { const bool COLLISION = std::ranges::any_of( entries_, [&](const Entry& e) { return e.pretty_name == base; }); if (!COLLISION) { return base; } // Format " #N" començant a #2 — la primera ocurrència manté el nom net. for (int i = 2; i < 100; ++i) { std::string candidate = base + " #" + std::to_string(i); const bool MATCH = std::ranges::any_of( entries_, [&](const Entry& e) { return e.pretty_name == candidate; }); if (!MATCH) { return candidate; } } return base; // cas ultra-improbable: 99 mandos idèntics connectats } void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) { installWebStandardMapping(jid); if (!SDL_IsGamepad(jid)) { return; } SDL_Gamepad* pad = SDL_OpenGamepad(jid); if (pad == nullptr) { std::cout << "GamepadManager: SDL_OpenGamepad(" << jid << ") ha fallat: " << SDL_GetError() << '\n'; return; } std::string name = uniqueName(prettyName(SDL_GetGamepadName(pad))); std::cout << "GamepadManager: connectat " << name << '\n'; entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name}); if (notify) { Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_connected")); } } void GamepadManager::removeGamepad(SDL_JoystickID jid) { auto it = std::ranges::find_if( entries_, [jid](const Entry& e) { return e.instance_id == jid; }); if (it == entries_.end()) { return; } std::string name = std::move(it->pretty_name); if (it->pad != nullptr) { SDL_CloseGamepad(it->pad); } std::cout << "GamepadManager: desconnectat " << name << '\n'; entries_.erase(it); Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_disconnected")); } void GamepadManager::init() { if (initialized_) { return; } if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) { std::cout << "GamepadManager: SDL_INIT_GAMEPAD ha fallat: " << SDL_GetError() << '\n'; return; } // `gamecontrollerdb.txt` és opcional. Si no existeix, SDL retorna -1 i // continuem amb les definicions internes del sistema. const int ADDED = SDL_AddGamepadMappingsFromFile("data/input/gamecontrollerdb.txt"); if (ADDED > 0) { std::cout << "GamepadManager: " << ADDED << " mappings de gamecontrollerdb.txt\n"; } int count = 0; SDL_JoystickID* ids = SDL_GetJoysticks(&count); if (ids != nullptr) { for (int i = 0; i < count; ++i) { addGamepad(ids[i], /*notify=*/false); } SDL_free(ids); } initialized_ = true; } void GamepadManager::shutdown() { for (auto& entry : entries_) { if (entry.pad != nullptr) { SDL_CloseGamepad(entry.pad); } } entries_.clear(); if (initialized_) { SDL_QuitSubSystem(SDL_INIT_GAMEPAD); initialized_ = false; } } void GamepadManager::handleEvent(const SDL_Event& ev) { if (ev.type == SDL_EVENT_GAMEPAD_ADDED) { addGamepad(ev.gdevice.which, /*notify=*/true); } else if (ev.type == SDL_EVENT_GAMEPAD_REMOVED) { removeGamepad(ev.gdevice.which); } } auto GamepadManager::getPrimary() const -> SDL_Gamepad* { return entries_.empty() ? nullptr : entries_.front().pad; } auto GamepadManager::getCount() const -> std::size_t { return entries_.size(); } auto GamepadManager::getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad* { return idx < entries_.size() ? entries_[idx].pad : nullptr; }