From 51d47a1455d174da5fc13799751fb05de8c2cac2 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Fri, 25 Mar 2022 22:15:53 +1000 Subject: [PATCH] InputManager: Add generic input mappings --- pcsx2/Frontend/InputManager.cpp | 55 ++++++++++++++++++++ pcsx2/Frontend/InputManager.h | 47 +++++++++++++++++ pcsx2/Frontend/InputSource.h | 4 ++ pcsx2/Frontend/SDLInputSource.cpp | 85 +++++++++++++++++++++++++++++++ pcsx2/Frontend/SDLInputSource.h | 1 + pcsx2/Frontend/XInputSource.cpp | 64 +++++++++++++++++++++++ pcsx2/Frontend/XInputSource.h | 1 + 7 files changed, 257 insertions(+) diff --git a/pcsx2/Frontend/InputManager.cpp b/pcsx2/Frontend/InputManager.cpp index fafa10245d..bb47ccda9d 100644 --- a/pcsx2/Frontend/InputManager.cpp +++ b/pcsx2/Frontend/InputManager.cpp @@ -935,6 +935,61 @@ std::vector InputManager::EnumerateMotors() return ret; } +static void GetKeyboardGenericBindingMapping(std::vector>* mapping) +{ + mapping->emplace_back(GenericInputBinding::DPadUp, "Keyboard/Up"); + mapping->emplace_back(GenericInputBinding::DPadRight, "Keyboard/Right"); + mapping->emplace_back(GenericInputBinding::DPadDown, "Keyboard/Down"); + mapping->emplace_back(GenericInputBinding::DPadLeft, "Keyboard/Left"); + mapping->emplace_back(GenericInputBinding::LeftStickUp, "Keyboard/W"); + mapping->emplace_back(GenericInputBinding::LeftStickRight, "Keyboard/D"); + mapping->emplace_back(GenericInputBinding::LeftStickDown, "Keyboard/S"); + mapping->emplace_back(GenericInputBinding::LeftStickLeft, "Keyboard/A"); + mapping->emplace_back(GenericInputBinding::RightStickUp, "Keyboard/T"); + mapping->emplace_back(GenericInputBinding::RightStickRight, "Keyboard/H"); + mapping->emplace_back(GenericInputBinding::RightStickDown, "Keyboard/G"); + mapping->emplace_back(GenericInputBinding::RightStickLeft, "Keyboard/F"); + mapping->emplace_back(GenericInputBinding::Start, "Keyboard/Return"); + mapping->emplace_back(GenericInputBinding::Select, "Keyboard/Backspace"); + mapping->emplace_back(GenericInputBinding::Triangle, "Keyboard/I"); + mapping->emplace_back(GenericInputBinding::Circle, "Keyboard/L"); + mapping->emplace_back(GenericInputBinding::Cross, "Keyboard/K"); + mapping->emplace_back(GenericInputBinding::Square, "Keyboard/J"); + mapping->emplace_back(GenericInputBinding::L1, "Keyboard/Q"); + mapping->emplace_back(GenericInputBinding::L2, "Keyboard/1"); + mapping->emplace_back(GenericInputBinding::L3, "Keyboard/2"); + mapping->emplace_back(GenericInputBinding::R1, "Keyboard/E"); + mapping->emplace_back(GenericInputBinding::R2, "Keyboard/2"); + mapping->emplace_back(GenericInputBinding::R3, "Keyboard/4"); +} + +static bool GetInternalGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) +{ + if (device == "Keyboard") + { + GetKeyboardGenericBindingMapping(mapping); + return true; + } + + return false; +} + +GenericInputBindingMapping InputManager::GetGenericBindingMapping(const std::string_view& device) +{ + GenericInputBindingMapping mapping; + + if (!GetInternalGenericBindingMapping(device, &mapping)) + { + for (u32 i = FIRST_EXTERNAL_INPUT_SOURCE; i < LAST_EXTERNAL_INPUT_SOURCE; i++) + { + if (s_input_sources[i] && s_input_sources[i]->GetGenericBindingMapping(device, &mapping)) + break; + } + } + + return mapping; +} + template static void UpdateInputSourceState(SettingsInterface& si, InputSourceType type, bool default_state) { diff --git a/pcsx2/Frontend/InputManager.h b/pcsx2/Frontend/InputManager.h index c1d707ad2a..b01c1880a8 100644 --- a/pcsx2/Frontend/InputManager.h +++ b/pcsx2/Frontend/InputManager.h @@ -125,6 +125,50 @@ DECLARE_HOTKEY_LIST(g_vm_manager_hotkeys); DECLARE_HOTKEY_LIST(g_gs_hotkeys); DECLARE_HOTKEY_LIST(g_host_hotkeys); +/// Generic input bindings. These roughly match a DualShock 4 or XBox One controller. +/// They are used for automatic binding to PS2 controller types. +enum class GenericInputBinding : u8 +{ + Unknown, + + DPadUp, + DPadRight, + DPadLeft, + DPadDown, + + LeftStickUp, + LeftStickRight, + LeftStickDown, + LeftStickLeft, + L3, + + RightStickUp, + RightStickRight, + RightStickDown, + RightStickLeft, + R3, + + Triangle, // Y on XBox pads. + Circle, // B on XBox pads. + Cross, // A on XBox pads. + Square, // X on XBox pads. + + Select, // Share on DS4, View on XBox pads. + Start, // Options on DS4, Menu on XBox pads. + System, // PS button on DS4, Guide button on XBox pads. + + L1, // LB on Xbox pads. + L2, // Left trigger on XBox pads. + R1, // RB on XBox pads. + R2, // Right trigger on Xbox pads. + + SmallMotor, // High frequency vibration. + LargeMotor, // Low frequency vibration. + + Count, +}; +using GenericInputBindingMapping = std::vector>; + /// External input source class. class InputSource; @@ -178,6 +222,9 @@ namespace InputManager /// Enumerates available vibration motors at the time of call. std::vector EnumerateMotors(); + /// Retrieves bindings that match the generic bindings for the specified device. + GenericInputBindingMapping GetGenericBindingMapping(const std::string_view& device); + /// Re-parses the config and registers all hotkey and pad bindings. void ReloadBindings(SettingsInterface& si); diff --git a/pcsx2/Frontend/InputSource.h b/pcsx2/Frontend/InputSource.h index a2e024db5e..caf98c58e0 100644 --- a/pcsx2/Frontend/InputSource.h +++ b/pcsx2/Frontend/InputSource.h @@ -47,6 +47,10 @@ public: /// Enumerates available vibration motors at the time of call. virtual std::vector EnumerateMotors() = 0; + /// Retrieves bindings that match the generic bindings for the specified device. + /// Returns false if it's not one of our devices. + virtual bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) = 0; + /// Informs the source of a new vibration motor state. Changes may not take effect until the next PollEvents() call. virtual void UpdateMotorState(InputBindingKey key, float intensity) = 0; diff --git a/pcsx2/Frontend/SDLInputSource.cpp b/pcsx2/Frontend/SDLInputSource.cpp index e8b5d67b3b..360f89904b 100644 --- a/pcsx2/Frontend/SDLInputSource.cpp +++ b/pcsx2/Frontend/SDLInputSource.cpp @@ -31,6 +31,14 @@ 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] = { + {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // SDL_CONTROLLER_AXIS_LEFTX + {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // SDL_CONTROLLER_AXIS_LEFTY + {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // SDL_CONTROLLER_AXIS_RIGHTX + {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // SDL_CONTROLLER_AXIS_RIGHTY + {GenericInputBinding::Unknown, GenericInputBinding::L2}, // SDL_CONTROLLER_AXIS_TRIGGERLEFT + {GenericInputBinding::Unknown, GenericInputBinding::R2}, // SDL_CONTROLLER_AXIS_TRIGGERRIGHT +}; static const char* s_sdl_button_names[] = { "A", // SDL_CONTROLLER_BUTTON_A @@ -55,6 +63,29 @@ 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[] = { + GenericInputBinding::Cross, // SDL_CONTROLLER_BUTTON_A + GenericInputBinding::Circle, // SDL_CONTROLLER_BUTTON_B + GenericInputBinding::Square, // SDL_CONTROLLER_BUTTON_X + GenericInputBinding::Triangle, // SDL_CONTROLLER_BUTTON_Y + GenericInputBinding::Select, // SDL_CONTROLLER_BUTTON_BACK + GenericInputBinding::System, // SDL_CONTROLLER_BUTTON_GUIDE + GenericInputBinding::Start, // SDL_CONTROLLER_BUTTON_START + GenericInputBinding::L3, // SDL_CONTROLLER_BUTTON_LEFTSTICK + GenericInputBinding::R3, // SDL_CONTROLLER_BUTTON_RIGHTSTICK + GenericInputBinding::L1, // SDL_CONTROLLER_BUTTON_LEFTSHOULDER + GenericInputBinding::R1, // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER + GenericInputBinding::DPadUp, // SDL_CONTROLLER_BUTTON_DPAD_UP + GenericInputBinding::DPadDown, // SDL_CONTROLLER_BUTTON_DPAD_DOWN + GenericInputBinding::DPadLeft, // SDL_CONTROLLER_BUTTON_DPAD_LEFT + GenericInputBinding::DPadRight, // SDL_CONTROLLER_BUTTON_DPAD_RIGHT + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_MISC1 + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE1 + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE2 + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE3 + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_PADDLE4 + GenericInputBinding::Unknown, // SDL_CONTROLLER_BUTTON_TOUCHPAD +}; SDLInputSource::SDLInputSource() = default; @@ -478,6 +509,60 @@ std::vector SDLInputSource::EnumerateMotors() return ret; } +bool SDLInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) +{ + if (!StringUtil::StartsWith(device, "SDL-")) + return false; + + const std::optional player_id = StringUtil::FromChars(device.substr(4)); + if (!player_id.has_value() || player_id.value() < 0) + return false; + + ControllerDataVector::iterator it = GetControllerDataForPlayerId(player_id.value()); + if (it == m_controllers.end()) + return false; + + if (it->game_controller) + { + // assume all buttons are present. + const s32 pid = player_id.value(); + for (u32 i = 0; i < std::size(s_sdl_generic_binding_axis_mapping); i++) + { + const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0]; + const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1]; + if (negative != GenericInputBinding::Unknown) + mapping->emplace_back(negative, StringUtil::StdStringFromFormat("SDL-%d/-%s", pid, s_sdl_axis_names[i])); + + if (positive != GenericInputBinding::Unknown) + mapping->emplace_back(positive, StringUtil::StdStringFromFormat("SDL-%d/+%s", pid, s_sdl_axis_names[i])); + } + for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) + { + const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; + if (binding != GenericInputBinding::Unknown) + mapping->emplace_back(binding, StringUtil::StdStringFromFormat("SDL-%d/%s", pid, s_sdl_button_names[i])); + } + + if (it->use_game_controller_rumble || it->haptic_left_right_effect) + { + mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("SDL-%d/SmallMotor", pid)); + mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("SDL-%d/LargeMotor", pid)); + } + else + { + mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("SDL-%d/Haptic", pid)); + mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("SDL-%d/Haptic", pid)); + } + + return true; + } + else + { + // joysticks, which we haven't implemented yet anyway. + return false; + } +} + void SDLInputSource::UpdateMotorState(InputBindingKey key, float intensity) { if (key.source_subtype != InputSubclass::ControllerMotor && key.source_subtype != InputSubclass::ControllerHaptic) diff --git a/pcsx2/Frontend/SDLInputSource.h b/pcsx2/Frontend/SDLInputSource.h index ac473c1e28..42020130c5 100644 --- a/pcsx2/Frontend/SDLInputSource.h +++ b/pcsx2/Frontend/SDLInputSource.h @@ -36,6 +36,7 @@ public: void PollEvents() override; std::vector> EnumerateDevices() override; std::vector EnumerateMotors() override; + bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override; void UpdateMotorState(InputBindingKey key, float intensity) override; void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; diff --git a/pcsx2/Frontend/XInputSource.cpp b/pcsx2/Frontend/XInputSource.cpp index 658b4421b4..b790feffa9 100644 --- a/pcsx2/Frontend/XInputSource.cpp +++ b/pcsx2/Frontend/XInputSource.cpp @@ -29,6 +29,14 @@ const char* XInputSource::s_axis_names[XInputSource::NUM_AXES] = { "LeftTrigger", // AXIS_TRIGGERLEFT "RightTrigger", // AXIS_TRIGGERRIGHT }; +static const GenericInputBinding s_sdl_generic_binding_axis_mapping[][2] = { + {GenericInputBinding::LeftStickLeft, GenericInputBinding::LeftStickRight}, // AXIS_LEFTX + {GenericInputBinding::LeftStickUp, GenericInputBinding::LeftStickDown}, // AXIS_LEFTY + {GenericInputBinding::RightStickLeft, GenericInputBinding::RightStickRight}, // AXIS_RIGHTX + {GenericInputBinding::RightStickUp, GenericInputBinding::RightStickDown}, // AXIS_RIGHTY + {GenericInputBinding::Unknown, GenericInputBinding::L2}, // AXIS_TRIGGERLEFT + {GenericInputBinding::Unknown, GenericInputBinding::R2}, // AXIS_TRIGGERRIGHT +}; const char* XInputSource::s_button_names[XInputSource::NUM_BUTTONS] = { "DPadUp", // XINPUT_GAMEPAD_DPAD_UP @@ -64,6 +72,23 @@ const u16 XInputSource::s_button_masks[XInputSource::NUM_BUTTONS] = { XINPUT_GAMEPAD_Y, 0x400, // XINPUT_GAMEPAD_GUIDE }; +static const GenericInputBinding s_sdl_generic_binding_button_mapping[] = { + GenericInputBinding::DPadUp, // XINPUT_GAMEPAD_DPAD_UP + GenericInputBinding::DPadDown, // XINPUT_GAMEPAD_DPAD_DOWN + GenericInputBinding::DPadLeft, // XINPUT_GAMEPAD_DPAD_LEFT + GenericInputBinding::DPadRight, // XINPUT_GAMEPAD_DPAD_RIGHT + GenericInputBinding::Start, // XINPUT_GAMEPAD_START + GenericInputBinding::Select, // XINPUT_GAMEPAD_BACK + GenericInputBinding::L3, // XINPUT_GAMEPAD_LEFT_THUMB + GenericInputBinding::R3, // XINPUT_GAMEPAD_RIGHT_THUMB + GenericInputBinding::L1, // XINPUT_GAMEPAD_LEFT_SHOULDER + GenericInputBinding::R1, // XINPUT_GAMEPAD_RIGHT_SHOULDER + GenericInputBinding::Cross, // XINPUT_GAMEPAD_A + GenericInputBinding::Circle, // XINPUT_GAMEPAD_B + GenericInputBinding::Square, // XINPUT_GAMEPAD_X + GenericInputBinding::Triangle, // XINPUT_GAMEPAD_Y + GenericInputBinding::System, // XINPUT_GAMEPAD_GUIDE +}; XInputSource::XInputSource() = default; @@ -286,6 +311,45 @@ std::vector XInputSource::EnumerateMotors() return ret; } +bool XInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) +{ + if (!StringUtil::StartsWith(device, "XInput-")) + return false; + + const std::optional player_id = StringUtil::FromChars(device.substr(7)); + if (!player_id.has_value() || player_id.value() < 0) + return false; + + if (player_id.value() < 0 || player_id.value() >= static_cast(XUSER_MAX_COUNT)) + return false; + + // assume all buttons are present. + const s32 pid = player_id.value(); + for (u32 i = 0; i < std::size(s_sdl_generic_binding_axis_mapping); i++) + { + const GenericInputBinding negative = s_sdl_generic_binding_axis_mapping[i][0]; + const GenericInputBinding positive = s_sdl_generic_binding_axis_mapping[i][1]; + if (negative != GenericInputBinding::Unknown) + mapping->emplace_back(negative, StringUtil::StdStringFromFormat("XInput-%d/-%s", pid, s_axis_names[i])); + + if (positive != GenericInputBinding::Unknown) + mapping->emplace_back(positive, StringUtil::StdStringFromFormat("XInput-%d/+%s", pid, s_axis_names[i])); + } + for (u32 i = 0; i < std::size(s_sdl_generic_binding_button_mapping); i++) + { + const GenericInputBinding binding = s_sdl_generic_binding_button_mapping[i]; + if (binding != GenericInputBinding::Unknown) + mapping->emplace_back(binding, StringUtil::StdStringFromFormat("XInput-%d/%s", pid, s_button_names[i])); + } + + if (m_controllers[pid].has_small_motor) + mapping->emplace_back(GenericInputBinding::SmallMotor, StringUtil::StdStringFromFormat("XInput-%d/SmallMotor", pid)); + if (m_controllers[pid].has_large_motor) + mapping->emplace_back(GenericInputBinding::LargeMotor, StringUtil::StdStringFromFormat("XInput-%d/LargeMotor", pid)); + + return true; +} + void XInputSource::HandleControllerConnection(u32 index) { Console.WriteLn("XInput controller %u connected.", index); diff --git a/pcsx2/Frontend/XInputSource.h b/pcsx2/Frontend/XInputSource.h index 0d3ab3d6ec..419ad42c7a 100644 --- a/pcsx2/Frontend/XInputSource.h +++ b/pcsx2/Frontend/XInputSource.h @@ -36,6 +36,7 @@ public: void PollEvents() override; std::vector> EnumerateDevices() override; std::vector EnumerateMotors() override; + bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override; void UpdateMotorState(InputBindingKey key, float intensity) override; void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override;