From ce16653cc81a1298a34741a7af4808da988a190f Mon Sep 17 00:00:00 2001 From: Vitor K Date: Fri, 1 Jan 2021 06:01:07 -0300 Subject: [PATCH] Automatic Controller Binding (#5100) * Implement the basics of controller auto mapping. From testing doesn't currenlty work. Opening the controller requires the device index, but it is only known and guaranteed at boot time or when a controller is connected. * Use the SDL_INIT_GAMECONTROLLER flag to initialize the controller subsystem. It automatically initializes the joystick subsystem too, so SDL_INIT_JOYSTICK is not needed. * Implement the SDLGameController class to handle open game controllers. Based on the SDLJoystick implementation. * Address review comments * Changes SDLJoystick and SDLGameController to use a custom default constructible destructor, to improve readability. The only deleters used previously were SDL_JoystickClose and SDL_GameControllerClose, respectively, plus null lambdas. Given that both SDL functions accept null pointers with just an early return, this should be functionally the same. with just an early return * warn the user when a controller mapping is not found * Get axis direction and threshold from SDL_ExtendedGameControllerBind * Reject analog bind if it's not axis, for the couple of examples present in SDL2.0.10's db. Also add SDL_CONTROLLER_BINDTYPE_NONE for the button bind switch, with a better log message. * sdl_impl.cpp: Log the error returned by SDL_GetError upon failure to open joystick * sdl: only use extended binding on SDL2.0.6 and up * sdl_impl.cpp: minor changes --- .../configuration/configure_input.cpp | 47 +++ src/citra_qt/configuration/configure_input.h | 3 + src/citra_qt/configuration/configure_input.ui | 29 +- src/input_common/main.cpp | 13 + src/input_common/main.h | 5 + src/input_common/sdl/sdl_impl.cpp | 348 +++++++++++++++++- src/input_common/sdl/sdl_impl.h | 19 + 7 files changed, 438 insertions(+), 26 deletions(-) diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index b5fd9bda2..85eeb5f61 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -276,6 +276,7 @@ ConfigureInput::ConfigureInput(QWidget* parent) ui->buttonDelete->setEnabled(ui->profile->count() > 1); + connect(ui->buttonAutoMap, &QPushButton::clicked, this, &ConfigureInput::AutoMap); connect(ui->buttonClearAll, &QPushButton::clicked, this, &ConfigureInput::ClearAll); connect(ui->buttonRestoreDefaults, &QPushButton::clicked, this, &ConfigureInput::RestoreDefaults); @@ -440,6 +441,52 @@ void ConfigureInput::UpdateButtonLabels() { EmitInputKeysChanged(); } +void ConfigureInput::MapFromButton(const Common::ParamPackage& params) { + Common::ParamPackage aux_param; + bool mapped = false; + for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { + aux_param = InputCommon::GetSDLControllerButtonBindByGUID(params.Get("guid", "0"), + params.Get("port", 0), button_id); + if (aux_param.Has("engine")) { + buttons_param[button_id] = aux_param; + mapped = true; + } + } + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { + aux_param = InputCommon::GetSDLControllerAnalogBindByGUID(params.Get("guid", "0"), + params.Get("port", 0), analog_id); + if (aux_param.Has("engine")) { + analogs_param[analog_id] = aux_param; + mapped = true; + } + } + if (!mapped) { + QMessageBox::warning( + this, tr("Warning"), + tr("Auto mapping failed. Your controller may not have a corresponding mapping")); + } +} + +void ConfigureInput::AutoMap() { + if (QMessageBox::information(this, tr("Information"), + tr("After pressing OK, press any button on your joystick"), + QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + input_setter = [=](const Common::ParamPackage& params) { + MapFromButton(params); + ApplyConfiguration(); + Settings::SaveProfile(ui->profile->currentIndex()); + }; + device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button); + want_keyboard_keys = false; + for (auto& poller : device_pollers) { + poller->Start(); + } + timeout_timer->start(5000); // Cancel after 5 seconds + poll_timer->start(200); // Check for new inputs every 200ms +} + void ConfigureInput::HandleClick(QPushButton* button, std::function new_input_setter, InputCommon::Polling::DeviceType type) { diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index fa9555563..a08510fb1 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -98,6 +98,9 @@ private: /// Generates list of all used keys QList GetUsedKeyboardKeys(); + void MapFromButton(const Common::ParamPackage& params); + void AutoMap(); + /// Restore all buttons to their default values. void RestoreDefaults(); /// Clear all input configuration diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui index 3a76730ad..63e5d5538 100644 --- a/src/citra_qt/configuration/configure_input.ui +++ b/src/citra_qt/configuration/configure_input.ui @@ -727,17 +727,32 @@ - - - Qt::Horizontal + + + + 0 + 0 + - + - 40 - 20 + 0 + 0 - + + + 0 + 0 + + + + Qt::LeftToRight + + + Auto Map + + diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 7aa15a9b4..89e441ddb 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -10,6 +10,7 @@ #include "input_common/main.h" #include "input_common/motion_emu.h" #include "input_common/sdl/sdl.h" +#include "input_common/sdl/sdl_impl.h" #include "input_common/touch_from_button.h" #include "input_common/udp/udp.h" @@ -76,6 +77,18 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, return circle_pad_param.Serialize(); } +Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, + int button) { + return dynamic_cast(sdl.get())->GetSDLControllerButtonBindByGUID( + guid, port, static_cast(button)); +} + +Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, + int analog) { + return dynamic_cast(sdl.get())->GetSDLControllerAnalogBindByGUID( + guid, port, static_cast(analog)); +} + void ReloadInputDevices() { if (udp) udp->ReloadUDPClient(); diff --git a/src/input_common/main.h b/src/input_common/main.h index d1229b207..606b198a8 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -37,6 +37,11 @@ std::string GenerateKeyboardParam(int key_code); std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, int key_modifier, float modifier_scale); +Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, + int button); +Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, + int analog); + /// Reloads the input devices void ReloadInputDevices(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index eaa64151d..362a91bb9 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -23,6 +23,54 @@ #include "core/frontend/input.h" #include "input_common/sdl/sdl_impl.h" +// These structures are not actually defined in the headers, so we need to define them here to use +// them. +typedef struct { + SDL_GameControllerBindType inputType; + union { + int button; + + struct { + int axis; + int axis_min; + int axis_max; + } axis; + + struct { + int hat; + int hat_mask; + } hat; + + } input; + + SDL_GameControllerBindType outputType; + union { + SDL_GameControllerButton button; + + struct { + SDL_GameControllerAxis axis; + int axis_min; + int axis_max; + } axis; + + } output; + +} SDL_ExtendedGameControllerBind; + +struct _SDL_GameController { + SDL_Joystick* joystick; /* underlying joystick device */ + int ref_count; + + const char* name; + int num_bindings; + SDL_ExtendedGameControllerBind* bindings; + SDL_ExtendedGameControllerBind** last_match_axis; + Uint8* last_hat_mask; + Uint32 guide_button_down; + + struct _SDL_GameController* next; /* pointer to next game controller we have allocated */ +}; + namespace InputCommon { namespace SDL { @@ -48,11 +96,36 @@ static int SDLEventWatcher(void* userdata, SDL_Event* event) { return 0; } +constexpr std::array + xinput_to_3ds_mapping = {{ + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_INVALID, + SDL_CONTROLLER_BUTTON_INVALID, + SDL_CONTROLLER_BUTTON_INVALID, + SDL_CONTROLLER_BUTTON_INVALID, + SDL_CONTROLLER_BUTTON_GUIDE, + }}; + +struct SDLJoystickDeleter { + void operator()(SDL_Joystick* object) { + SDL_JoystickClose(object); + } +}; class SDLJoystick { public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick} {} void SetButton(int button, bool value) { std::lock_guard lock{mutex}; @@ -118,10 +191,12 @@ public: return sdl_joystick.get(); } - void SetSDLJoystick(SDL_Joystick* joystick, - decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { - sdl_joystick = - std::unique_ptr(joystick, deleter); + void SetSDLJoystick(SDL_Joystick* joystick) { + sdl_joystick = std::unique_ptr(joystick); + } + + SDL_GameController* GetGameController() const { + return SDL_GameControllerFromInstanceID(SDL_JoystickInstanceID(sdl_joystick.get())); } private: @@ -132,10 +207,48 @@ private: } state; std::string guid; int port; - std::unique_ptr sdl_joystick; + std::unique_ptr sdl_joystick; mutable std::mutex mutex; }; +struct SDLGameControllerDeleter { + void operator()(SDL_GameController* object) { + SDL_GameControllerClose(object); + } +}; +class SDLGameController { +public: + SDLGameController(std::string guid_, int port_, SDL_GameController* controller) + : guid{std::move(guid_)}, port{port_}, sdl_controller{controller} {} + + /** + * The guid of the joystick/controller + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + + void SetSDLGameController(SDL_GameController* controller) { + sdl_controller = std::unique_ptr(controller); + } + +private: + std::string guid; + int port; + std::unique_ptr sdl_controller; +}; + /** * Get the nth joystick with the corresponding GUID */ @@ -144,16 +257,32 @@ std::shared_ptr SDLState::GetSDLJoystickByGUID(const std::string& g const auto it = joystick_map.find(guid); if (it != joystick_map.end()) { while (it->second.size() <= static_cast(port)) { - auto joystick = std::make_shared(guid, static_cast(it->second.size()), - nullptr, [](SDL_Joystick*) {}); + auto joystick = + std::make_shared(guid, static_cast(it->second.size()), nullptr); it->second.emplace_back(std::move(joystick)); } return it->second[port]; } - auto joystick = std::make_shared(guid, 0, nullptr, [](SDL_Joystick*) {}); + auto joystick = std::make_shared(guid, 0, nullptr); return joystick_map[guid].emplace_back(std::move(joystick)); } +std::shared_ptr SDLState::GetSDLGameControllerByGUID(const std::string& guid, + int port) { + std::lock_guard lock{controller_map_mutex}; + const auto it = controller_map.find(guid); + if (it != controller_map.end()) { + while (it->second.size() <= static_cast(port)) { + auto controller = std::make_shared( + guid, static_cast(it->second.size()), nullptr); + it->second.emplace_back(std::move(controller)); + } + return it->second[port]; + } + auto controller = std::make_shared(guid, 0, nullptr); + return controller_map[guid].emplace_back(std::move(controller)); +} + /** * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie * it to a SDLJoystick with the same guid and that port @@ -193,10 +322,129 @@ std::shared_ptr SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_ return joystick_map[guid].emplace_back(std::move(joystick)); } +Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID( + const std::string& guid, int port, Settings::NativeButton::Values button) { + Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("guid", guid); + params.Set("port", port); + SDL_GameController* controller = GetSDLGameControllerByGUID(guid, port)->GetSDLGameController(); + SDL_GameControllerButtonBind button_bind; + + if (!controller) { + LOG_WARNING(Input, "failed to open controller {}", guid); + return {{}}; + } + + auto mapped_button = xinput_to_3ds_mapping[static_cast(button)]; + if (mapped_button == SDL_CONTROLLER_BUTTON_INVALID) { + if (button == Settings::NativeButton::Values::ZL) { + button_bind = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + } else if (button == Settings::NativeButton::Values::ZR) { + button_bind = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + } else { + return {{}}; + } + } else { + button_bind = SDL_GameControllerGetBindForButton(controller, mapped_button); + } + + switch (button_bind.bindType) { + case SDL_CONTROLLER_BINDTYPE_BUTTON: + params.Set("button", button_bind.value.button); + break; + case SDL_CONTROLLER_BINDTYPE_HAT: + params.Set("hat", button_bind.value.hat.hat); + switch (button_bind.value.hat.hat_mask) { + case SDL_HAT_UP: + params.Set("direction", "up"); + break; + case SDL_HAT_DOWN: + params.Set("direction", "down"); + break; + case SDL_HAT_LEFT: + params.Set("direction", "left"); + break; + case SDL_HAT_RIGHT: + params.Set("direction", "right"); + break; + default: + return {{}}; + } + break; + case SDL_CONTROLLER_BINDTYPE_AXIS: + params.Set("axis", button_bind.value.axis); + +#if SDL_VERSION_ATLEAST(2, 0, 6) + { + const SDL_ExtendedGameControllerBind extended_bind = + controller->bindings[mapped_button]; + if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) { + params.Set("direction", "-"); + } else { + params.Set("direction", "+"); + } + params.Set( + "threshold", + (extended_bind.input.axis.axis_min + + (extended_bind.input.axis.axis_max - extended_bind.input.axis.axis_min) / 2.0f) / + SDL_JOYSTICK_AXIS_MAX); + } +#else + params.Set("direction", "+"); // lacks extended_bind, so just a guess +#endif + break; + case SDL_CONTROLLER_BINDTYPE_NONE: + LOG_WARNING(Input, "Button not bound: {}", Settings::NativeButton::mapping[button]); + return {{}}; + default: + LOG_WARNING(Input, "unknown SDL bind type {}", button_bind.bindType); + return {{}}; + } + + return params; +} + +Common::ParamPackage SDLState::GetSDLControllerAnalogBindByGUID( + const std::string& guid, int port, Settings::NativeAnalog::Values analog) { + Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("guid", guid); + params.Set("port", port); + SDL_GameController* controller = GetSDLGameControllerByGUID(guid, port)->GetSDLGameController(); + SDL_GameControllerButtonBind button_bind_x; + SDL_GameControllerButtonBind button_bind_y; + + if (!controller) { + LOG_WARNING(Input, "failed to open controller {}", guid); + return {{}}; + } + + if (analog == Settings::NativeAnalog::Values::CirclePad) { + button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + } else if (analog == Settings::NativeAnalog::Values::CStick) { + button_bind_x = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + button_bind_y = SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + } else { + LOG_WARNING(Input, "analog value out of range {}", analog); + return {{}}; + } + + if (button_bind_x.bindType != SDL_CONTROLLER_BINDTYPE_AXIS || + button_bind_y.bindType != SDL_CONTROLLER_BINDTYPE_AXIS) { + return {{}}; + } + params.Set("axis_x", button_bind_x.value.axis); + params.Set("axis_y", button_bind_y.value.axis); + return params; +} + void SDLState::InitJoystick(int joystick_index) { SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); if (!sdl_joystick) { - LOG_ERROR(Input, "failed to open joystick {}", joystick_index); + LOG_ERROR(Input, "failed to open joystick {}, with error: {}", joystick_index, + SDL_GetError()); return; } const std::string guid = GetGUID(sdl_joystick); @@ -219,6 +467,35 @@ void SDLState::InitJoystick(int joystick_index) { joystick_guid_list.emplace_back(std::move(joystick)); } +void SDLState::InitGameController(int controller_index) { + SDL_GameController* sdl_controller = SDL_GameControllerOpen(controller_index); + if (!sdl_controller) { + LOG_WARNING(Input, "failed to open joystick {} as controller", controller_index); + return; + } + const std::string guid = GetGUID(SDL_GameControllerGetJoystick(sdl_controller)); + + LOG_INFO(Input, "opened joystick {} as controller", controller_index); + std::lock_guard lock{controller_map_mutex}; + if (controller_map.find(guid) == controller_map.end()) { + auto controller = std::make_shared(guid, 0, sdl_controller); + controller_map[guid].emplace_back(std::move(controller)); + return; + } + auto& controller_guid_list = controller_map[guid]; + const auto it = std::find_if(controller_guid_list.begin(), controller_guid_list.end(), + [](const std::shared_ptr& controller) { + return !controller->GetSDLGameController(); + }); + if (it != controller_guid_list.end()) { + (*it)->SetSDLGameController(sdl_controller); + return; + } + auto controller = + std::make_shared(guid, controller_guid_list.size(), sdl_controller); + controller_guid_list.emplace_back(std::move(controller)); +} + void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { std::string guid = GetGUID(sdl_joystick); std::shared_ptr joystick; @@ -235,7 +512,23 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { } // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback // which locks the mutex again - joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); + joystick->SetSDLJoystick(nullptr); +} + +void SDLState::CloseGameController(SDL_GameController* sdl_controller) { + std::string guid = GetGUID(SDL_GameControllerGetJoystick(sdl_controller)); + std::shared_ptr controller; + { + std::lock_guard lock{controller_map_mutex}; + auto& controller_guid_list = controller_map[guid]; + const auto controller_it = + std::find_if(controller_guid_list.begin(), controller_guid_list.end(), + [&sdl_controller](const std::shared_ptr& controller) { + return controller->GetSDLGameController() == sdl_controller; + }); + controller = *controller_it; + } + controller->SetSDLGameController(nullptr); } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -265,13 +558,21 @@ void SDLState::HandleGameControllerEvent(const SDL_Event& event) { break; } case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + LOG_DEBUG(Input, "Joystick removed with Instance_ID {}", event.jdevice.which); CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); break; case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + LOG_DEBUG(Input, "Joystick connected with device index {}", event.jdevice.which); InitJoystick(event.jdevice.which); break; + case SDL_CONTROLLERDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.cdevice.which); + CloseGameController(SDL_GameControllerFromInstanceID(event.cdevice.which)); + break; + case SDL_CONTROLLERDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.cdevice.which); + InitGameController(event.cdevice.which); + break; } } @@ -280,6 +581,11 @@ void SDLState::CloseJoysticks() { joystick_map.clear(); } +void SDLState::CloseGameControllers() { + std::lock_guard lock{controller_map_mutex}; + controller_map.clear(); +} + class SDLButton final : public Input::ButtonDevice { public: explicit SDLButton(std::shared_ptr joystick_, int button_) @@ -464,9 +770,9 @@ SDLState::SDLState() { RegisterFactory("sdl", std::make_shared(*this)); // If the frontend is going to manage the event loop, then we dont start one here - start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); - if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); + start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER); + if (start_thread && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed with: {}", SDL_GetError()); return; } if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { @@ -495,6 +801,9 @@ SDLState::SDLState() { // Because the events for joystick connection happens before we have our event watcher added, we // can just open all the joysticks right here for (int i = 0; i < SDL_NumJoysticks(); ++i) { + if (SDL_IsGameController(i)) { + InitGameController(i); + } InitJoystick(i); } } @@ -505,12 +814,13 @@ SDLState::~SDLState() { UnregisterFactory("sdl"); CloseJoysticks(); + CloseGameControllers(); SDL_DelEventWatch(&SDLEventWatcher, this); initialized = false; if (start_thread) { poll_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); } } diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index 2579741d6..05f627d0f 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -8,15 +8,18 @@ #include #include #include "common/threadsafe_queue.h" +#include "core/settings.h" #include "input_common/sdl/sdl.h" union SDL_Event; using SDL_Joystick = struct _SDL_Joystick; using SDL_JoystickID = s32; +using SDL_GameController = struct _SDL_GameController; namespace InputCommon::SDL { class SDLJoystick; +class SDLGameController; class SDLButtonFactory; class SDLAnalogFactory; @@ -34,6 +37,14 @@ public: std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); + std::shared_ptr GetSDLGameControllerByGUID(const std::string& guid, + int port); + + Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port, + Settings::NativeButton::Values button); + Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port, + Settings::NativeAnalog::Values analog); + /// Get all DevicePoller that use the SDL backend for a specific device type Pollers GetPollers(Polling::DeviceType type) override; @@ -45,13 +56,21 @@ private: void InitJoystick(int joystick_index); void CloseJoystick(SDL_Joystick* sdl_joystick); + void InitGameController(int joystick_index); + void CloseGameController(SDL_GameController* sdl_controller); + /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + void CloseGameControllers(); /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map>> joystick_map; std::mutex joystick_map_mutex; + /// Map of GUID of a list of corresponding virtual Controllers + std::unordered_map>> controller_map; + std::mutex controller_map_mutex; + std::shared_ptr button_factory; std::shared_ptr analog_factory;