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;