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
This commit is contained in:
Vitor K 2021-01-01 06:01:07 -03:00 committed by GitHub
parent 6f2bbbcced
commit ce16653cc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 438 additions and 26 deletions

View File

@ -276,6 +276,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ui->buttonDelete->setEnabled(ui->profile->count() > 1); 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->buttonClearAll, &QPushButton::clicked, this, &ConfigureInput::ClearAll);
connect(ui->buttonRestoreDefaults, &QPushButton::clicked, this, connect(ui->buttonRestoreDefaults, &QPushButton::clicked, this,
&ConfigureInput::RestoreDefaults); &ConfigureInput::RestoreDefaults);
@ -440,6 +441,52 @@ void ConfigureInput::UpdateButtonLabels() {
EmitInputKeysChanged(); 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, void ConfigureInput::HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) { InputCommon::Polling::DeviceType type) {

View File

@ -98,6 +98,9 @@ private:
/// Generates list of all used keys /// Generates list of all used keys
QList<QKeySequence> GetUsedKeyboardKeys(); QList<QKeySequence> GetUsedKeyboardKeys();
void MapFromButton(const Common::ParamPackage& params);
void AutoMap();
/// Restore all buttons to their default values. /// Restore all buttons to their default values.
void RestoreDefaults(); void RestoreDefaults();
/// Clear all input configuration /// Clear all input configuration

View File

@ -727,17 +727,32 @@
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <widget class="QPushButton" name="buttonAutoMap">
<property name="orientation"> <property name="sizePolicy">
<enum>Qt::Horizontal</enum> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeIncrement">
<size> <size>
<width>40</width> <width>0</width>
<height>20</height> <height>0</height>
</size> </size>
</property> </property>
</spacer> <property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Auto Map</string>
</property>
</widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="buttonClearAll"> <widget class="QPushButton" name="buttonClearAll">

View File

@ -10,6 +10,7 @@
#include "input_common/main.h" #include "input_common/main.h"
#include "input_common/motion_emu.h" #include "input_common/motion_emu.h"
#include "input_common/sdl/sdl.h" #include "input_common/sdl/sdl.h"
#include "input_common/sdl/sdl_impl.h"
#include "input_common/touch_from_button.h" #include "input_common/touch_from_button.h"
#include "input_common/udp/udp.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(); return circle_pad_param.Serialize();
} }
Common::ParamPackage GetSDLControllerButtonBindByGUID(const std::string& guid, int port,
int button) {
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerButtonBindByGUID(
guid, port, static_cast<Settings::NativeButton::Values>(button));
}
Common::ParamPackage GetSDLControllerAnalogBindByGUID(const std::string& guid, int port,
int analog) {
return dynamic_cast<SDL::SDLState*>(sdl.get())->GetSDLControllerAnalogBindByGUID(
guid, port, static_cast<Settings::NativeAnalog::Values>(analog));
}
void ReloadInputDevices() { void ReloadInputDevices() {
if (udp) if (udp)
udp->ReloadUDPClient(); udp->ReloadUDPClient();

View File

@ -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, std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right,
int key_modifier, float modifier_scale); 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 /// Reloads the input devices
void ReloadInputDevices(); void ReloadInputDevices();

View File

@ -23,6 +23,54 @@
#include "core/frontend/input.h" #include "core/frontend/input.h"
#include "input_common/sdl/sdl_impl.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 InputCommon {
namespace SDL { namespace SDL {
@ -48,11 +96,36 @@ static int SDLEventWatcher(void* userdata, SDL_Event* event) {
return 0; return 0;
} }
constexpr std::array<SDL_GameControllerButton, Settings::NativeButton::NumButtons>
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 { class SDLJoystick {
public: public:
SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick)
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick} {}
: guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
void SetButton(int button, bool value) { void SetButton(int button, bool value) {
std::lock_guard lock{mutex}; std::lock_guard lock{mutex};
@ -118,10 +191,12 @@ public:
return sdl_joystick.get(); return sdl_joystick.get();
} }
void SetSDLJoystick(SDL_Joystick* joystick, void SetSDLJoystick(SDL_Joystick* joystick) {
decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { sdl_joystick = std::unique_ptr<SDL_Joystick, SDLJoystickDeleter>(joystick);
sdl_joystick = }
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
SDL_GameController* GetGameController() const {
return SDL_GameControllerFromInstanceID(SDL_JoystickInstanceID(sdl_joystick.get()));
} }
private: private:
@ -132,10 +207,48 @@ private:
} state; } state;
std::string guid; std::string guid;
int port; int port;
std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; std::unique_ptr<SDL_Joystick, SDLJoystickDeleter> sdl_joystick;
mutable std::mutex mutex; 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<SDL_GameController, SDLGameControllerDeleter>(controller);
}
private:
std::string guid;
int port;
std::unique_ptr<SDL_GameController, SDLGameControllerDeleter> sdl_controller;
};
/** /**
* Get the nth joystick with the corresponding GUID * Get the nth joystick with the corresponding GUID
*/ */
@ -144,16 +257,32 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g
const auto it = joystick_map.find(guid); const auto it = joystick_map.find(guid);
if (it != joystick_map.end()) { if (it != joystick_map.end()) {
while (it->second.size() <= static_cast<std::size_t>(port)) { while (it->second.size() <= static_cast<std::size_t>(port)) {
auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), auto joystick =
nullptr, [](SDL_Joystick*) {}); std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr);
it->second.emplace_back(std::move(joystick)); it->second.emplace_back(std::move(joystick));
} }
return it->second[port]; return it->second[port];
} }
auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr);
return joystick_map[guid].emplace_back(std::move(joystick)); return joystick_map[guid].emplace_back(std::move(joystick));
} }
std::shared_ptr<SDLGameController> 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<std::size_t>(port)) {
auto controller = std::make_shared<SDLGameController>(
guid, static_cast<int>(it->second.size()), nullptr);
it->second.emplace_back(std::move(controller));
}
return it->second[port];
}
auto controller = std::make_shared<SDLGameController>(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 * 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 * it to a SDLJoystick with the same guid and that port
@ -193,10 +322,129 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_
return joystick_map[guid].emplace_back(std::move(joystick)); 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<int>(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) { void SDLState::InitJoystick(int joystick_index) {
SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
if (!sdl_joystick) { 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; return;
} }
const std::string guid = GetGUID(sdl_joystick); 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)); 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<SDLGameController>(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<SDLGameController>& controller) {
return !controller->GetSDLGameController();
});
if (it != controller_guid_list.end()) {
(*it)->SetSDLGameController(sdl_controller);
return;
}
auto controller =
std::make_shared<SDLGameController>(guid, controller_guid_list.size(), sdl_controller);
controller_guid_list.emplace_back(std::move(controller));
}
void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
std::string guid = GetGUID(sdl_joystick); std::string guid = GetGUID(sdl_joystick);
std::shared_ptr<SDLJoystick> joystick; std::shared_ptr<SDLJoystick> 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 // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback
// which locks the mutex again // 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<SDLGameController> 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<SDLGameController>& controller) {
return controller->GetSDLGameController() == sdl_controller;
});
controller = *controller_it;
}
controller->SetSDLGameController(nullptr);
} }
void SDLState::HandleGameControllerEvent(const SDL_Event& event) { void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
@ -265,13 +558,21 @@ void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
break; break;
} }
case SDL_JOYDEVICEREMOVED: 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)); CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
break; break;
case SDL_JOYDEVICEADDED: 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); InitJoystick(event.jdevice.which);
break; 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(); joystick_map.clear();
} }
void SDLState::CloseGameControllers() {
std::lock_guard lock{controller_map_mutex};
controller_map.clear();
}
class SDLButton final : public Input::ButtonDevice { class SDLButton final : public Input::ButtonDevice {
public: public:
explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
@ -464,9 +770,9 @@ SDLState::SDLState() {
RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
// If the frontend is going to manage the event loop, then we dont start one here // If the frontend is going to manage the event loop, then we dont start one here
start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); start_thread = !SDL_WasInit(SDL_INIT_GAMECONTROLLER);
if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { if (start_thread && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed with: {}", SDL_GetError());
return; return;
} }
if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { 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 // Because the events for joystick connection happens before we have our event watcher added, we
// can just open all the joysticks right here // can just open all the joysticks right here
for (int i = 0; i < SDL_NumJoysticks(); ++i) { for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) {
InitGameController(i);
}
InitJoystick(i); InitJoystick(i);
} }
} }
@ -505,12 +814,13 @@ SDLState::~SDLState() {
UnregisterFactory<AnalogDevice>("sdl"); UnregisterFactory<AnalogDevice>("sdl");
CloseJoysticks(); CloseJoysticks();
CloseGameControllers();
SDL_DelEventWatch(&SDLEventWatcher, this); SDL_DelEventWatch(&SDLEventWatcher, this);
initialized = false; initialized = false;
if (start_thread) { if (start_thread) {
poll_thread.join(); poll_thread.join();
SDL_QuitSubSystem(SDL_INIT_JOYSTICK); SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
} }
} }

View File

@ -8,15 +8,18 @@
#include <memory> #include <memory>
#include <thread> #include <thread>
#include "common/threadsafe_queue.h" #include "common/threadsafe_queue.h"
#include "core/settings.h"
#include "input_common/sdl/sdl.h" #include "input_common/sdl/sdl.h"
union SDL_Event; union SDL_Event;
using SDL_Joystick = struct _SDL_Joystick; using SDL_Joystick = struct _SDL_Joystick;
using SDL_JoystickID = s32; using SDL_JoystickID = s32;
using SDL_GameController = struct _SDL_GameController;
namespace InputCommon::SDL { namespace InputCommon::SDL {
class SDLJoystick; class SDLJoystick;
class SDLGameController;
class SDLButtonFactory; class SDLButtonFactory;
class SDLAnalogFactory; class SDLAnalogFactory;
@ -34,6 +37,14 @@ public:
std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
std::shared_ptr<SDLGameController> 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 /// Get all DevicePoller that use the SDL backend for a specific device type
Pollers GetPollers(Polling::DeviceType type) override; Pollers GetPollers(Polling::DeviceType type) override;
@ -45,13 +56,21 @@ private:
void InitJoystick(int joystick_index); void InitJoystick(int joystick_index);
void CloseJoystick(SDL_Joystick* sdl_joystick); void CloseJoystick(SDL_Joystick* sdl_joystick);
void InitGameController(int joystick_index);
void CloseGameController(SDL_GameController* sdl_controller);
/// Needs to be called before SDL_QuitSubSystem. /// Needs to be called before SDL_QuitSubSystem.
void CloseJoysticks(); void CloseJoysticks();
void CloseGameControllers();
/// Map of GUID of a list of corresponding virtual Joysticks /// Map of GUID of a list of corresponding virtual Joysticks
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
std::mutex joystick_map_mutex; std::mutex joystick_map_mutex;
/// Map of GUID of a list of corresponding virtual Controllers
std::unordered_map<std::string, std::vector<std::shared_ptr<SDLGameController>>> controller_map;
std::mutex controller_map_mutex;
std::shared_ptr<SDLButtonFactory> button_factory; std::shared_ptr<SDLButtonFactory> button_factory;
std::shared_ptr<SDLAnalogFactory> analog_factory; std::shared_ptr<SDLAnalogFactory> analog_factory;