diff --git a/android/app/src/cpp/CMakeLists.txt b/android/app/src/cpp/CMakeLists.txt index bee415093..22d795e50 100644 --- a/android/app/src/cpp/CMakeLists.txt +++ b/android/app/src/cpp/CMakeLists.txt @@ -1,4 +1,6 @@ set(SRCS + android_controller_interface.cpp + android_controller_interface.h android_host_interface.cpp android_host_interface.h android_progress_callback.cpp diff --git a/android/app/src/cpp/android_controller_interface.cpp b/android/app/src/cpp/android_controller_interface.cpp new file mode 100644 index 000000000..e9d5241c9 --- /dev/null +++ b/android/app/src/cpp/android_controller_interface.cpp @@ -0,0 +1,194 @@ +#include "android_controller_interface.h" +#include "common/assert.h" +#include "common/file_system.h" +#include "common/log.h" +#include "core/controller.h" +#include "core/host_interface.h" +#include "core/system.h" +#include +Log_SetChannel(AndroidControllerInterface); + +AndroidControllerInterface::AndroidControllerInterface() = default; + +AndroidControllerInterface::~AndroidControllerInterface() = default; + +ControllerInterface::Backend AndroidControllerInterface::GetBackend() const +{ + return ControllerInterface::Backend::Android; +} + +bool AndroidControllerInterface::Initialize(CommonHostInterface* host_interface) +{ + if (!ControllerInterface::Initialize(host_interface)) + return false; + + return true; +} + +void AndroidControllerInterface::Shutdown() +{ + ControllerInterface::Shutdown(); +} + +void AndroidControllerInterface::PollEvents() {} + +void AndroidControllerInterface::ClearBindings() +{ + for (ControllerData& cd : m_controllers) + { + cd.axis_mapping.fill({}); + cd.button_mapping.fill({}); + cd.axis_button_mapping.fill({}); + cd.button_axis_mapping.fill({}); + } +} + +bool AndroidControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, + AxisCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size()) + return false; + + if (axis_number < 0 || axis_number >= NUM_AXISES) + return false; + + m_controllers[controller_index].axis_mapping[axis_number][axis_side] = std::move(callback); + return true; +} + +bool AndroidControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size()) + return false; + + if (button_number < 0 || button_number >= NUM_BUTTONS) + return false; + + m_controllers[controller_index].button_mapping[button_number] = std::move(callback); + return true; +} + +bool AndroidControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction, + ButtonCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size()) + return false; + + if (axis_number < 0 || axis_number >= NUM_AXISES) + return false; + + m_controllers[controller_index].axis_button_mapping[axis_number][BoolToUInt8(direction)] = std::move(callback); + return true; +} + +bool AndroidControllerInterface::BindControllerHatToButton(int controller_index, int hat_number, + std::string_view hat_position, ButtonCallback callback) +{ + // Hats don't exist in XInput + return false; +} + +bool AndroidControllerInterface::BindControllerButtonToAxis(int controller_index, int button_number, + AxisCallback callback) +{ + if (static_cast(controller_index) >= m_controllers.size()) + return false; + + if (button_number < 0 || button_number >= NUM_BUTTONS) + return false; + + m_controllers[controller_index].button_axis_mapping[button_number] = std::move(callback); + return true; +} + +bool AndroidControllerInterface::HandleAxisEvent(u32 index, u32 axis, float value) +{ + Log_DevPrintf("controller %u axis %u %f", index, static_cast(axis), value); + DebugAssert(index < NUM_CONTROLLERS); + + if (DoEventHook(Hook::Type::Axis, index, static_cast(axis), value)) + return true; + + const AxisCallback& cb = m_controllers[index].axis_mapping[static_cast(axis)][AxisSide::Full]; + if (cb) + { + // Extend triggers from a 0 - 1 range to a -1 - 1 range for consistency with other inputs + if (axis == 4 && axis == 5) + { + cb((value * 2.0f) - 1.0f); + } + else + { + cb(value); + } + return true; + } + + // set the other direction to false so large movements don't leave the opposite on + const bool outside_deadzone = (std::abs(value) >= m_controllers[index].deadzone); + const bool positive = (value >= 0.0f); + const ButtonCallback& other_button_cb = + m_controllers[index].axis_button_mapping[static_cast(axis)][BoolToUInt8(!positive)]; + const ButtonCallback& button_cb = + m_controllers[index].axis_button_mapping[static_cast(axis)][BoolToUInt8(positive)]; + if (button_cb) + { + button_cb(outside_deadzone); + if (other_button_cb) + other_button_cb(false); + return true; + } + else if (other_button_cb) + { + other_button_cb(false); + return true; + } + else + { + return false; + } +} + +bool AndroidControllerInterface::HandleButtonEvent(u32 index, u32 button, bool pressed) +{ + Log_DevPrintf("controller %u button %u %s", index, button, pressed ? "pressed" : "released"); + DebugAssert(index < NUM_CONTROLLERS); + + if (DoEventHook(Hook::Type::Button, index, button, pressed ? 1.0f : 0.0f)) + return true; + + const ButtonCallback& cb = m_controllers[index].button_mapping[button]; + if (cb) + { + cb(pressed); + return true; + } + + const AxisCallback& axis_cb = m_controllers[index].button_axis_mapping[button]; + if (axis_cb) + { + axis_cb(pressed ? 1.0f : -1.0f); + } + return true; +} + +u32 AndroidControllerInterface::GetControllerRumbleMotorCount(int controller_index) +{ + return 0; +} + +void AndroidControllerInterface::SetControllerRumbleStrength(int controller_index, const float* strengths, + u32 num_motors) +{ +} + +bool AndroidControllerInterface::SetControllerDeadzone(int controller_index, float size /* = 0.25f */) +{ + if (static_cast(controller_index) >= NUM_CONTROLLERS) + return false; + + m_controllers[static_cast(controller_index)].deadzone = std::clamp(std::abs(size), 0.01f, 0.99f); + Log_InfoPrintf("Controller %d deadzone size set to %f", controller_index, + m_controllers[static_cast(controller_index)].deadzone); + return true; +} diff --git a/android/app/src/cpp/android_controller_interface.h b/android/app/src/cpp/android_controller_interface.h new file mode 100644 index 000000000..a0888dfed --- /dev/null +++ b/android/app/src/cpp/android_controller_interface.h @@ -0,0 +1,67 @@ +#pragma once +#include "frontend-common/controller_interface.h" +#include "core/types.h" +#include +#include +#include +#include + +class AndroidControllerInterface final : public ControllerInterface +{ +public: + AndroidControllerInterface(); + ~AndroidControllerInterface() override; + + Backend GetBackend() const override; + bool Initialize(CommonHostInterface* host_interface) override; + void Shutdown() override; + + // Removes all bindings. Call before setting new bindings. + void ClearBindings() override; + + // Binding to events. If a binding for this axis/button already exists, returns false. + bool BindControllerAxis(int controller_index, int axis_number, AxisSide axis_side, AxisCallback callback) override; + bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback) override; + bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, + ButtonCallback callback) override; + bool BindControllerHatToButton(int controller_index, int hat_number, std::string_view hat_position, + ButtonCallback callback) override; + bool BindControllerButtonToAxis(int controller_index, int button_number, AxisCallback callback) override; + + // Changing rumble strength. + u32 GetControllerRumbleMotorCount(int controller_index) override; + void SetControllerRumbleStrength(int controller_index, const float* strengths, u32 num_motors) override; + + // Set deadzone that will be applied on axis-to-button mappings + bool SetControllerDeadzone(int controller_index, float size = 0.25f) override; + + void PollEvents() override; + + bool HandleAxisEvent(u32 index, u32 axis, float value); + bool HandleButtonEvent(u32 index, u32 button, bool pressed); + +private: + enum : u32 + { + NUM_CONTROLLERS = 1, + NUM_AXISES = 10, + NUM_BUTTONS = 18 + }; + + struct ControllerData + { + float deadzone = 0.25f; + + std::array, NUM_AXISES> axis_mapping; + std::array button_mapping; + std::array, NUM_AXISES> axis_button_mapping; + std::array button_axis_mapping; + }; + + using ControllerDataArray = std::array; + + ControllerDataArray m_controllers; + + std::mutex m_event_intercept_mutex; + Hook::Callback m_event_intercept_callback; +}; diff --git a/src/frontend-common/controller_interface.cpp b/src/frontend-common/controller_interface.cpp index 9a0730457..50c9e1119 100644 --- a/src/frontend-common/controller_interface.cpp +++ b/src/frontend-common/controller_interface.cpp @@ -91,6 +91,10 @@ static constexpr std::array(ControllerInterface::B TRANSLATABLE("ControllerInterface", "XInput"), TRANSLATABLE("ControllerInterface", "DInput"), #endif +#ifdef ANDROID + // Deliberately not translated as it's not exposed to users. + "Android", +#endif }}; std::optional ControllerInterface::ParseBackendName(const char* name) diff --git a/src/frontend-common/controller_interface.h b/src/frontend-common/controller_interface.h index 5b012b264..d3091fb16 100644 --- a/src/frontend-common/controller_interface.h +++ b/src/frontend-common/controller_interface.h @@ -23,6 +23,9 @@ public: #ifdef WIN32 XInput, DInput, +#endif +#ifdef ANDROID + Android, #endif Count };