From 4ebb5a87b2e4b015bccdccfecddf6581aa160409 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 16 Dec 2022 21:59:37 +1000 Subject: [PATCH] SDLInputSource: Support joysticks as well as controllers --- common/StringUtil.h | 38 +++++ pcsx2/Frontend/InputManager.h | 5 +- pcsx2/Frontend/InputSource.cpp | 10 ++ pcsx2/Frontend/InputSource.h | 4 + pcsx2/Frontend/SDLInputSource.cpp | 226 ++++++++++++++++++++++++------ pcsx2/Frontend/SDLInputSource.h | 19 ++- 6 files changed, 248 insertions(+), 54 deletions(-) diff --git a/common/StringUtil.h b/common/StringUtil.h index b9db36b8f8..e0195b2c10 100644 --- a/common/StringUtil.h +++ b/common/StringUtil.h @@ -88,6 +88,25 @@ namespace StringUtil return value; } + template ::value, bool> = true> + inline std::optional FromChars(const std::string_view& str, int base, std::string_view* endptr) + { + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const std::from_chars_result result = std::from_chars(ptr, end, value, base); + if (result.ec != std::errc()) + return std::nullopt; + + if (endptr) + { + const size_t remaining_len = end - ptr - 1; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } + + return value; + } template ::value, bool> = true> inline std::optional FromChars(const std::string_view& str) @@ -100,6 +119,25 @@ namespace StringUtil return value; } + template ::value, bool> = true> + inline std::optional FromChars(const std::string_view& str, std::string_view* endptr) + { + T value; + + const char* ptr = str.data(); + const char* end = ptr + str.length(); + const fast_float::from_chars_result result = fast_float::from_chars(ptr, end, value); + if (result.ec != std::errc()) + return std::nullopt; + + if (endptr) + { + const size_t remaining_len = end - ptr - 1; + *endptr = (remaining_len > 0) ? std::string_view(result.ptr, remaining_len) : std::string_view(); + } + + return value; + } /// Wrapper around std::to_chars template ::value, bool> = true> diff --git a/pcsx2/Frontend/InputManager.h b/pcsx2/Frontend/InputManager.h index 1838e5e095..83862a8726 100644 --- a/pcsx2/Frontend/InputManager.h +++ b/pcsx2/Frontend/InputManager.h @@ -53,8 +53,9 @@ enum class InputSubclass : u32 ControllerButton = 0, ControllerAxis = 1, - ControllerMotor = 2, - ControllerHaptic = 3, + ControllerHat = 2, + ControllerMotor = 3, + ControllerHaptic = 4, }; enum class InputModifier : u32 diff --git a/pcsx2/Frontend/InputSource.cpp b/pcsx2/Frontend/InputSource.cpp index 6d195e9924..5d8f7d441b 100644 --- a/pcsx2/Frontend/InputSource.cpp +++ b/pcsx2/Frontend/InputSource.cpp @@ -50,6 +50,16 @@ InputBindingKey InputSource::MakeGenericControllerButtonKey( return key; } +InputBindingKey InputSource::MakeGenericControllerHatKey(InputSourceType clazz, u32 controller_index, s32 hat_index, u8 hat_direction, u32 num_directions) +{ + InputBindingKey key = {}; + key.source_type = clazz; + key.source_index = controller_index; + key.source_subtype = InputSubclass::ControllerHat; + key.data = static_cast(hat_index) * num_directions + hat_direction; + return key; +} + InputBindingKey InputSource::MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index) { InputBindingKey key = {}; diff --git a/pcsx2/Frontend/InputSource.h b/pcsx2/Frontend/InputSource.h index 863725e75a..a6288623a2 100644 --- a/pcsx2/Frontend/InputSource.h +++ b/pcsx2/Frontend/InputSource.h @@ -63,6 +63,10 @@ public: /// Creates a key for a generic controller button event. static InputBindingKey MakeGenericControllerButtonKey(InputSourceType clazz, u32 controller_index, s32 button_index); + /// Creates a key for a generic controller hat event. + static InputBindingKey MakeGenericControllerHatKey( + InputSourceType clazz, u32 controller_index, s32 hat_index, u8 hat_direction, u32 num_directions); + /// Creates a key for a generic controller motor event. static InputBindingKey MakeGenericControllerMotorKey(InputSourceType clazz, u32 controller_index, s32 motor_index); diff --git a/pcsx2/Frontend/SDLInputSource.cpp b/pcsx2/Frontend/SDLInputSource.cpp index d53a1d4f1f..eb0a4462fe 100644 --- a/pcsx2/Frontend/SDLInputSource.cpp +++ b/pcsx2/Frontend/SDLInputSource.cpp @@ -23,7 +23,9 @@ #include "common/Console.h" #include -static const char* s_sdl_axis_names[] = { +#include "GS/GSIntrin.h" // _BitScanForward + +static constexpr const char* s_sdl_axis_names[] = { "LeftX", // SDL_CONTROLLER_AXIS_LEFTX "LeftY", // SDL_CONTROLLER_AXIS_LEFTY "RightX", // SDL_CONTROLLER_AXIS_RIGHTX @@ -31,7 +33,7 @@ static const char* s_sdl_axis_names[] = { "LeftTrigger", // SDL_CONTROLLER_AXIS_TRIGGERLEFT "RightTrigger", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT }; -static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { +static constexpr const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX @@ -40,7 +42,7 @@ static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT }; -static const char* s_sdl_button_names[] = { +static constexpr const char* s_sdl_button_names[] = { "A", // SDL_CONTROLLER_BUTTON_A "B", // SDL_CONTROLLER_BUTTON_B "X", // SDL_CONTROLLER_BUTTON_X @@ -63,7 +65,7 @@ static const char* s_sdl_button_names[] = { "Paddle4", // SDL_CONTROLLER_BUTTON_PADDLE4 "Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD }; -static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { +static constexpr const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X @@ -87,6 +89,15 @@ static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD }; +static constexpr const char* s_sdl_hat_direction_names[] = { + // clang-format off + "North", + "East", + "South", + "West", + // clang-format on +}; + SDLInputSource::SDLInputSource() = default; SDLInputSource::~SDLInputSource() @@ -171,7 +182,7 @@ bool SDLInputSource::InitializeSubsystem() void SDLInputSource::ShutdownSubsystem() { while (!m_controllers.empty()) - CloseGameController(m_controllers.begin()->joystick_id); + CloseDevice(m_controllers.begin()->joystick_id); if (m_sdl_subsystem_initialized) { @@ -200,7 +211,7 @@ std::vector> SDLInputSource::EnumerateDevice { std::string id(StringUtil::StdStringFromFormat("SDL-%d", cd.player_id)); - const char* name = SDL_GameControllerName(cd.game_controller); + const char* name = cd.game_controller ? SDL_GameControllerName(cd.game_controller) : SDL_JoystickName(cd.joystick); if (name) ret.emplace_back(std::move(id), name); else @@ -276,14 +287,32 @@ std::optional SDLInputSource::ParseKeyString(const std::string_ } else if (StringUtil::StartsWith(binding, "FullAxis")) { - if (auto value = StringUtil::FromChars(binding.substr(8))) + std::string_view end; + if (auto value = StringUtil::FromChars(binding.substr(8), 10, &end)) { key.source_subtype = InputSubclass::ControllerAxis; key.data = *value; key.modifier = InputModifier::FullAxis; + key.invert = (end == "~"); return key; } } + else if (StringUtil::StartsWith(binding, "Hat")) + { + std::string_view hat_dir; + if (auto value = StringUtil::FromChars(binding.substr(3), 10, &hat_dir); value.has_value() && !hat_dir.empty()) + { + for (u8 dir = 0; dir < static_cast(std::size(s_sdl_hat_direction_names)); dir++) + { + if (hat_dir == s_sdl_hat_direction_names[dir]) + { + key.source_subtype = InputSubclass::ControllerHat; + key.data = value.value() * std::size(s_sdl_hat_direction_names) + dir; + return key; + } + } + } + } else { // must be a button @@ -338,6 +367,12 @@ std::string SDLInputSource::ConvertKeyToString(InputBindingKey key) else ret = StringUtil::StdStringFromFormat("SDL-%u/Button%u", key.source_index, key.data); } + else if (key.source_subtype == InputSubclass::ControllerHat) + { + const u32 hat_index = key.data / static_cast(std::size(s_sdl_hat_direction_names)); + const u32 hat_direction = key.data % static_cast(std::size(s_sdl_hat_direction_names)); + ret = StringUtil::StdStringFromFormat("SDL-%u/Hat%u%s", key.source_index, hat_index, s_sdl_hat_direction_names[hat_direction]); + } else if (key.source_subtype == InputSubclass::ControllerMotor) { ret = StringUtil::StdStringFromFormat("SDL-%u/%sMotor", key.source_index, key.data ? "Large" : "Small"); @@ -358,14 +393,36 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) case SDL_CONTROLLERDEVICEADDED: { Console.WriteLn("(SDLInputSource) Controller %d inserted", event->cdevice.which); - OpenGameController(event->cdevice.which); + OpenDevice(event->cdevice.which, true); return true; } case SDL_CONTROLLERDEVICEREMOVED: { Console.WriteLn("(SDLInputSource) Controller %d removed", event->cdevice.which); - CloseGameController(event->cdevice.which); + CloseDevice(event->cdevice.which); + return true; + } + + case SDL_JOYDEVICEADDED: + { + // Let game controller handle.. well.. game controllers. + if (SDL_IsGameController(event->jdevice.which)) + return false; + + Console.WriteLn("(SDLInputSource) Joystick %d inserted", event->jdevice.which); + OpenDevice(event->cdevice.which, false); + return true; + } + break; + + case SDL_JOYDEVICEREMOVED: + { + if (auto it = GetControllerDataForJoystickId(event->cdevice.which); it != m_controllers.end() && it->game_controller) + return false; + + Console.WriteLn("(SDLInputSource) Joystick %d removed", event->jdevice.which); + CloseDevice(event->cdevice.which); return true; } @@ -383,11 +440,30 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event) case SDL_JOYBUTTONUP: return HandleJoystickButtonEvent(&event->jbutton); + case SDL_JOYHATMOTION: + return HandleJoystickHatEvent(&event->jhat); + default: return false; } } +SDL_Joystick* SDLInputSource::GetJoystickForDevice(const std::string_view& device) +{ + if (!StringUtil::StartsWith(device, "SDL-")) + return nullptr; + + const std::optional player_id = StringUtil::FromChars(device.substr(4)); + if (!player_id.has_value() || player_id.value() < 0) + return nullptr; + + auto it = GetControllerDataForPlayerId(player_id.value()); + if (it == m_controllers.end()) + return nullptr; + + return it->joystick; +} + SDLInputSource::ControllerDataVector::iterator SDLInputSource::GetControllerDataForJoystickId(int id) { return std::find_if(m_controllers.begin(), m_controllers.end(), [id](const ControllerData& cd) { return cd.joystick_id == id; }); @@ -415,11 +491,23 @@ int SDLInputSource::GetFreePlayerId() const return 0; } -bool SDLInputSource::OpenGameController(int index) +bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller) { - SDL_GameController* gcontroller = SDL_GameControllerOpen(index); - SDL_Joystick* joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr; - if (!gcontroller || !joystick) + SDL_GameController* gcontroller; + SDL_Joystick* joystick; + + if (is_gamecontroller) + { + gcontroller = SDL_GameControllerOpen(index); + joystick = gcontroller ? SDL_GameControllerGetJoystick(gcontroller) : nullptr; + } + else + { + gcontroller = nullptr; + joystick = SDL_JoystickOpen(index); + } + + if (!gcontroller && !joystick) { Console.Error("(SDLInputSource) Failed to open controller %d", index); if (gcontroller) @@ -429,7 +517,7 @@ bool SDLInputSource::OpenGameController(int index) } const int joystick_id = SDL_JoystickInstanceID(joystick); - int player_id = SDL_GameControllerGetPlayerIndex(gcontroller); + int player_id = gcontroller ? SDL_GameControllerGetPlayerIndex(gcontroller) : SDL_JoystickGetPlayerIndex(joystick); if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end()) { const int free_player_id = GetFreePlayerId(); @@ -439,34 +527,49 @@ bool SDLInputSource::OpenGameController(int index) player_id = free_player_id; } - Console.WriteLn("(SDLInputSource) Opened controller %d (instance id %d, player id %d): %s", index, joystick_id, player_id, - SDL_GameControllerName(gcontroller)); + const char* name = gcontroller ? SDL_GameControllerName(gcontroller) : SDL_JoystickName(joystick); + if (!name) + name = "Unknown Device"; + + Console.WriteLn("(SDLInputSource) Opened %s %d (instance id %d, player id %d): %s", is_gamecontroller ? "game controller" : "joystick", + index, joystick_id, player_id, name); ControllerData cd = {}; cd.player_id = player_id; cd.joystick_id = joystick_id; cd.haptic_left_right_effect = -1; cd.game_controller = gcontroller; + cd.joystick = joystick; - const int num_axes = SDL_JoystickNumAxes(joystick); - const int num_buttons = SDL_JoystickNumButtons(joystick); - cd.joy_axis_used_in_gc.resize(num_axes, false); - cd.joy_button_used_in_gc.resize(num_buttons, false); - auto mark_bind = [&](SDL_GameControllerButtonBind bind) { - if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes) - cd.joy_axis_used_in_gc[bind.value.axis] = true; - if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons) - cd.joy_button_used_in_gc[bind.value.button] = true; - }; - for (size_t i = 0; i < std::size(s_sdl_axis_names); i++) - mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast(i))); - for (size_t i = 0; i < std::size(s_sdl_button_names); i++) - mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast(i))); + if (gcontroller) + { + const int num_axes = SDL_JoystickNumAxes(joystick); + const int num_buttons = SDL_JoystickNumButtons(joystick); + cd.joy_axis_used_in_gc.resize(num_axes, false); + cd.joy_button_used_in_gc.resize(num_buttons, false); + auto mark_bind = [&](SDL_GameControllerButtonBind bind) { + if (bind.bindType == SDL_CONTROLLER_BINDTYPE_AXIS && bind.value.axis < num_axes) + cd.joy_axis_used_in_gc[bind.value.axis] = true; + if (bind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON && bind.value.button < num_buttons) + cd.joy_button_used_in_gc[bind.value.button] = true; + }; + for (size_t i = 0; i < std::size(s_sdl_axis_names); i++) + mark_bind(SDL_GameControllerGetBindForAxis(gcontroller, static_cast(i))); + for (size_t i = 0; i < std::size(s_sdl_button_names); i++) + mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast(i))); + } + else + { + // GC doesn't have the concept of hats, so we only need to do this for joysticks. + const int num_hats = SDL_JoystickNumHats(joystick); + if (num_hats > 0) + cd.last_hat_state.resize(static_cast(num_hats), u8(0)); + } - cd.use_game_controller_rumble = (SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); + cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0); if (cd.use_game_controller_rumble) { - Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", SDL_GameControllerName(gcontroller)); + Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via gamecontroller", name); } else { @@ -499,34 +602,35 @@ bool SDLInputSource::OpenGameController(int index) } if (cd.haptic) - Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via haptic", SDL_GameControllerName(gcontroller)); + Console.WriteLn("(SDLInputSource) Rumble is supported on '%s' via haptic", name); } if (!cd.haptic && !cd.use_game_controller_rumble) - Console.Warning("(SDLInputSource) Rumble is not supported on '%s'", SDL_GameControllerName(gcontroller)); + Console.Warning("(SDLInputSource) Rumble is not supported on '%s'", name); m_controllers.push_back(std::move(cd)); - const char* name = SDL_GameControllerName(cd.game_controller); - Host::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name ? name : "Unknown Device"); + Host::OnInputDeviceConnected(StringUtil::StdStringFromFormat("SDL-%d", player_id), name); return true; } -bool SDLInputSource::CloseGameController(int joystick_index) +bool SDLInputSource::CloseDevice(int joystick_index) { auto it = GetControllerDataForJoystickId(joystick_index); if (it == m_controllers.end()) return false; + Host::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", it->player_id)); + if (it->haptic) - SDL_HapticClose(static_cast(it->haptic)); + SDL_HapticClose(it->haptic); - SDL_GameControllerClose(static_cast(it->game_controller)); + if (it->game_controller) + SDL_GameControllerClose(it->game_controller); + else + SDL_JoystickClose(it->joystick); - const int player_id = it->player_id; m_controllers.erase(it); - - Host::OnInputDeviceDisconnected(StringUtil::StdStringFromFormat("SDL-%d", player_id)); return true; } @@ -542,7 +646,8 @@ bool SDLInputSource::HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev return false; const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, ev->axis)); - return InputManager::InvokeEvents(key, NormalizeS16(ev->value)); + InputManager::InvokeEvents(key, NormalizeS16(ev->value)); + return true; } bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev) @@ -555,7 +660,8 @@ bool SDLInputSource::HandleControllerButtonEvent(const SDL_ControllerButtonEvent const GenericInputBinding generic_key = (ev->button < std::size(s_sdl_generic_binding_button_mapping)) ? s_sdl_generic_binding_button_mapping[ev->button] : GenericInputBinding::Unknown; - return InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); + InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f, generic_key); + return true; } bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev) @@ -567,7 +673,8 @@ bool SDLInputSource::HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev) return false; // Will get handled by GC event const u32 axis = ev->axis + std::size(s_sdl_axis_names); // Ensure we don't conflict with GC axes const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, axis)); - return InputManager::InvokeEvents(key, NormalizeS16(ev->value)); + InputManager::InvokeEvents(key, NormalizeS16(ev->value)); + return true; } bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev) @@ -579,7 +686,34 @@ bool SDLInputSource::HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev) return false; // Will get handled by GC event const u32 button = ev->button + std::size(s_sdl_button_names); // Ensure we don't conflict with GC buttons const InputBindingKey key(MakeGenericControllerAxisKey(InputSourceType::SDL, it->player_id, button)); - return InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f); + InputManager::InvokeEvents(key, (ev->state == SDL_PRESSED) ? 1.0f : 0.0f); + return true; +} + +bool SDLInputSource::HandleJoystickHatEvent(const SDL_JoyHatEvent* ev) +{ + auto it = GetControllerDataForJoystickId(ev->which); + if (it == m_controllers.end() || ev->hat >= it->last_hat_state.size()) + return false; + + const unsigned long last_direction = it->last_hat_state[ev->hat]; + it->last_hat_state[ev->hat] = ev->value; + + unsigned long changed_direction = last_direction ^ ev->value; + while (changed_direction != 0) + { + unsigned long pos; + _BitScanForward(&pos, changed_direction); + + const unsigned long mask = (1u << pos); + changed_direction &= ~mask; + + const InputBindingKey key( + MakeGenericControllerHatKey(InputSourceType::SDL, it->player_id, ev->hat, pos, std::size(s_sdl_hat_direction_names))); + InputManager::InvokeEvents(key, (last_direction & mask) ? 0.0f : 1.0f); + } + + return true; } std::vector SDLInputSource::EnumerateMotors() diff --git a/pcsx2/Frontend/SDLInputSource.h b/pcsx2/Frontend/SDLInputSource.h index ffe8592c00..a4b4a9378c 100644 --- a/pcsx2/Frontend/SDLInputSource.h +++ b/pcsx2/Frontend/SDLInputSource.h @@ -46,11 +46,14 @@ public: bool ProcessSDLEvent(const SDL_Event* event); + SDL_Joystick* GetJoystickForDevice(const std::string_view& device); + private: struct ControllerData { SDL_Haptic* haptic; SDL_GameController* game_controller; + SDL_Joystick* joystick; u16 rumble_intensity[2]; int haptic_left_right_effect; int joystick_id; @@ -60,6 +63,9 @@ private: // Used to disable Joystick controls that are used in GameController inputs so we don't get double events std::vector joy_button_used_in_gc; std::vector joy_axis_used_in_gc; + + // Track last hat state so we can send "unpressed" events. + std::vector last_hat_state; }; using ControllerDataVector = std::vector; @@ -73,12 +79,13 @@ private: ControllerDataVector::iterator GetControllerDataForPlayerId(int id); int GetFreePlayerId() const; - bool OpenGameController(int index); - bool CloseGameController(int joystick_index); - bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* event); - bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* event); - bool HandleJoystickAxisEvent(const SDL_JoyAxisEvent* event); - bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* event); + bool OpenDevice(int index, bool is_gamecontroller); + bool CloseDevice(int joystick_index); + bool HandleControllerAxisEvent(const SDL_ControllerAxisEvent* ev); + bool HandleControllerButtonEvent(const SDL_ControllerButtonEvent* ev); + bool HandleJoystickAxisEvent(const SDL_JoyAxisEvent* ev); + bool HandleJoystickButtonEvent(const SDL_JoyButtonEvent* ev); + bool HandleJoystickHatEvent(const SDL_JoyHatEvent* ev); void SendRumbleUpdate(ControllerData* cd); ControllerDataVector m_controllers;