feat(input): assignació mando↔jugador persistida a config.yaml
This commit is contained in:
1 parent
4e9d5df872
commit
a725b76684
5 files changed
+226
-13
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
@@ -87,8 +88,23 @@ void GamepadManager::addGamepad(SDL_JoystickID jid, bool notify) {
|
||||
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});
|
||||
// `SDL_GetGamepadPath` pot tornar null a backends que no exposen ruta
|
||||
// física (Emscripten, alguns drivers). Buit com a fallback; la
|
||||
// reconnexió es resoldrà per nom.
|
||||
const char* raw_path = SDL_GetGamepadPath(pad);
|
||||
std::string path = (raw_path != nullptr) ? raw_path : "";
|
||||
std::cout << "GamepadManager: connectat " << name
|
||||
<< (path.empty() ? "" : " [" + path + "]") << '\n';
|
||||
entries_.push_back(Entry{.pad = pad, .instance_id = jid, .pretty_name = name, .path = path});
|
||||
|
||||
// Hot-plug: si alguna assignació pendent encaixa amb aquest mando,
|
||||
// la vinculem ara. Primer intentem per path exacte, després per nom.
|
||||
for (auto& a : assignments_) {
|
||||
if (a.pad != nullptr) { continue; }
|
||||
if (a.name.empty() && a.path.empty()) { continue; }
|
||||
rebindAssignment(a);
|
||||
}
|
||||
|
||||
if (notify) {
|
||||
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_connected"));
|
||||
}
|
||||
@@ -100,9 +116,18 @@ void GamepadManager::removeGamepad(SDL_JoystickID jid) {
|
||||
[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); }
|
||||
SDL_Gamepad* pad = it->pad;
|
||||
if (pad != nullptr) { SDL_CloseGamepad(pad); }
|
||||
std::cout << "GamepadManager: desconnectat " << name << '\n';
|
||||
entries_.erase(it);
|
||||
|
||||
// Qualsevol assignació que apuntava a aquest mando queda amb `pad`
|
||||
// a null però conservem nom/ruta: si el mando es torna a connectar,
|
||||
// el hot-plug el re-vincularà automàticament.
|
||||
for (auto& a : assignments_) {
|
||||
if (a.pad == pad) { a.pad = nullptr; }
|
||||
}
|
||||
|
||||
Notification::notify(name + ' ' + Locale::get().text("notification.gamepad_disconnected"));
|
||||
}
|
||||
|
||||
@@ -161,3 +186,74 @@ auto GamepadManager::getCount() const -> std::size_t {
|
||||
auto GamepadManager::getByPlayerIndex(std::size_t idx) const -> SDL_Gamepad* {
|
||||
return idx < entries_.size() ? entries_[idx].pad : nullptr;
|
||||
}
|
||||
|
||||
auto GamepadManager::getAll() const -> std::vector<Info> {
|
||||
std::vector<Info> result;
|
||||
result.reserve(entries_.size());
|
||||
std::ranges::transform(entries_, std::back_inserter(result), [](const Entry& e) {
|
||||
return Info{.pad = e.pad, .name = e.pretty_name, .path = e.path};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
auto GamepadManager::getForPlayer(int player_id) const -> SDL_Gamepad* {
|
||||
if (player_id < 0 || player_id >= MAX_PLAYERS) { return nullptr; }
|
||||
return assignments_[static_cast<std::size_t>(player_id)].pad;
|
||||
}
|
||||
|
||||
void GamepadManager::rebindAssignment(Assignment& a) {
|
||||
// Primera passada: coincidència exacta per path. És el cas estable
|
||||
// (mateix mando, mateix port) i no canvia res de l'assignació.
|
||||
if (!a.path.empty()) {
|
||||
auto it = std::ranges::find_if(
|
||||
entries_,
|
||||
[&](const Entry& e) { return !e.path.empty() && e.path == a.path; });
|
||||
if (it != entries_.end()) {
|
||||
a.pad = it->pad;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Segona passada: coincidència per nom (mando canviat de port USB).
|
||||
// Acceptem la primera entrada que no estigui ja vinculada a una altra
|
||||
// assignació — així dos mandos amb el mateix nom no es pisen.
|
||||
if (!a.name.empty()) {
|
||||
for (const auto& e : entries_) {
|
||||
if (e.pretty_name != a.name) { continue; }
|
||||
const bool TAKEN = std::ranges::any_of(
|
||||
assignments_,
|
||||
[&](const Assignment& other) { return &other != &a && other.pad == e.pad; });
|
||||
if (TAKEN) { continue; }
|
||||
a.pad = e.pad;
|
||||
a.path = e.path; // actualitzem ruta persistida
|
||||
return;
|
||||
}
|
||||
}
|
||||
a.pad = nullptr;
|
||||
}
|
||||
|
||||
void GamepadManager::assignToPlayer(int player_id, const std::string& name, const std::string& path) {
|
||||
if (player_id < 0 || player_id >= MAX_PLAYERS) { return; }
|
||||
auto& a = assignments_[static_cast<std::size_t>(player_id)];
|
||||
a.name = name;
|
||||
a.path = path;
|
||||
a.pad = nullptr;
|
||||
if (name.empty() && path.empty()) { return; }
|
||||
rebindAssignment(a);
|
||||
// Last-wins: si l'altre jugador tenia el mateix mando vinculat, el
|
||||
// desvinculem (conservant el seu nom/path perquè el menú pugui
|
||||
// mostrar l'estat "pendent" i oferir re-selecció).
|
||||
if (a.pad != nullptr) {
|
||||
for (auto& other : assignments_) {
|
||||
if (&other != &a && other.pad == a.pad) {
|
||||
std::cout << "GamepadManager: mando " << a.name
|
||||
<< " reassignat (last-wins)\n";
|
||||
other.pad = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamepadManager::unassignFromPlayer(int player_id) {
|
||||
if (player_id < 0 || player_id >= MAX_PLAYERS) { return; }
|
||||
assignments_[static_cast<std::size_t>(player_id)] = Assignment{};
|
||||
}
|
||||
Reference in new issue
Block a user