input: add android input APIs

This commit is contained in:
SSimco 2026-05-09 17:49:19 +03:00
parent 8b1f104a57
commit 43d3766468
15 changed files with 704 additions and 0 deletions

View File

@ -88,6 +88,21 @@ if (SUPPORTS_WIIMOTE)
endif ()
if(ANDROID)
target_sources(CemuInput PRIVATE
api/Android/AndroidController.cpp
api/Android/AndroidController.h
api/Android/AndroidControllerProvider.cpp
api/Android/AndroidControllerProvider.h
api/Android/ControllerManager.cpp
api/Android/ControllerManager.h
api/Device/DeviceController.cpp
api/Device/DeviceController.h
api/Device/DeviceControllerProvider.cpp
api/Device/DeviceControllerProvider.h
)
endif()
if(ENABLE_SDL)
target_sources(CemuInput PRIVATE
api/SDL/SDLController.cpp

View File

@ -22,6 +22,11 @@
#include "input/api/SDL/SDLController.h"
#endif
#if BOOST_PLAT_ANDROID
#include "input/api/Android/AndroidController.h"
#include "input/api/Device/DeviceController.h"
#endif
ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::string_view uuid,
std::string_view display_name)
{
@ -104,6 +109,12 @@ ControllerPtr ControllerFactory::CreateController(InputAPI::Type api, std::strin
const auto index = ConvertString<uint32>(uuid);
return std::make_shared<NativeWiimoteController>(index);
}
#endif
#if BOOST_PLAT_ANDROID
case InputAPI::Android:
return std::make_shared<AndroidController>(uuid, display_name);
case InputAPI::Device:
return std::make_shared<DeviceController>();
#endif
default:
throw std::invalid_argument(fmt::format("unhandled controller api: {}", api));
@ -175,6 +186,12 @@ ControllerProviderPtr ControllerFactory::CreateControllerProvider(InputAPI::Type
#if HAS_WIIMOTE
case InputAPI::Wiimote:
return std::make_shared<WiimoteControllerProvider>();
#endif
#if BOOST_PLAT_ANDROID
case InputAPI::Android:
return std::make_shared<AndroidControllerProvider>();
case InputAPI::Device:
return std::make_shared<DeviceControllerProvider>();
#endif
default:
cemu_assert_debug(false);

View File

@ -46,6 +46,10 @@ InputManager::InputManager()
#if HAS_WIIMOTE
create_provider<WiimoteControllerProvider>();
#endif
#if BOOST_PLAT_ANDROID
create_provider<AndroidControllerProvider>();
create_provider<DeviceControllerProvider>();
#endif
m_update_thread_shutdown.store(false);
m_update_thread = std::thread(&InputManager::update_thread, this);

View File

@ -17,6 +17,11 @@
#include "input/api/DSU/DSUControllerProvider.h"
#include "input/api/GameCube/GameCubeControllerProvider.h"
#if BOOST_PLAT_ANDROID
#include "input/api/Android/AndroidControllerProvider.h"
#include "input/api/Device/DeviceControllerProvider.h"
#endif
#include "input/emulated/VPADController.h"
#include "input/emulated/WPADController.h"

View File

@ -0,0 +1,112 @@
#include "input/api/Android/AndroidController.h"
#include "ControllerManager.h"
#include <android/keycodes.h>
AndroidController::AndroidController(std::string_view device_descriptor, std::string_view device_name)
: base_type(device_descriptor, device_name)
{
m_state = ControllerManager::instance().get_state(m_uuid);
}
ControllerState AndroidController::raw_state()
{
std::shared_lock lock{m_state->mutex};
return m_state->inputs;
}
bool AndroidController::has_rumble()
{
std::shared_lock lock{m_state->mutex};
return m_state->hasRumble;
}
void AndroidController::start_rumble()
{
if (is_connected() && !has_rumble())
{
return;
}
if (m_settings.rumble <= 0 || m_settings.rumble > 1)
{
return;
}
ControllerManager::instance().vibrate_controller(
m_uuid,
5000,
static_cast<int>(255.0f * m_settings.rumble));
}
void AndroidController::stop_rumble()
{
if (is_connected() && !has_rumble())
{
return;
}
if (m_settings.rumble <= 0)
{
return;
}
ControllerManager::instance().cancel_controller_vibration(m_uuid);
}
MotionSample AndroidController::get_motion_sample()
{
std::shared_lock lock{m_state->mutex};
return m_state->motionHandler.getMotionSample();
}
bool AndroidController::has_motion()
{
std::shared_lock lock{m_state->mutex};
return m_state->hasMotion;
}
bool AndroidController::is_connected()
{
std::shared_lock lock{m_state->mutex};
return m_state->isConnected;
}
std::string AndroidController::get_button_name(uint64 button) const
{
switch (button)
{
case AKEYCODE_BUTTON_A: return "Button A";
case AKEYCODE_BUTTON_B: return "Button B";
case AKEYCODE_BUTTON_C: return "Button C";
case AKEYCODE_BUTTON_X: return "Button X";
case AKEYCODE_BUTTON_Y: return "Button Y";
case AKEYCODE_BUTTON_Z: return "Button Z";
case AKEYCODE_BUTTON_L1: return "Button L1";
case AKEYCODE_BUTTON_R1: return "Button R1";
case AKEYCODE_BUTTON_L2: return "Button L2";
case AKEYCODE_BUTTON_R2: return "Button R2";
case AKEYCODE_BUTTON_THUMBL: return "Button ThumbL";
case AKEYCODE_BUTTON_THUMBR: return "Button ThumbR";
case AKEYCODE_BUTTON_START: return "Button Start";
case AKEYCODE_BUTTON_SELECT: return "Button Select";
case AKEYCODE_BUTTON_MODE: return "Button Mode";
case AKEYCODE_BUTTON_1: return "Button 1";
case AKEYCODE_BUTTON_2: return "Button 2";
case AKEYCODE_BUTTON_3: return "Button 3";
case AKEYCODE_BUTTON_4: return "Button 4";
case AKEYCODE_BUTTON_5: return "Button 5";
case AKEYCODE_BUTTON_6: return "Button 6";
case AKEYCODE_BUTTON_7: return "Button 7";
case AKEYCODE_BUTTON_8: return "Button 8";
case AKEYCODE_BUTTON_9: return "Button 9";
case AKEYCODE_BUTTON_10: return "Button 10";
case AKEYCODE_BUTTON_11: return "Button 11";
case AKEYCODE_BUTTON_12: return "Button 12";
case AKEYCODE_BUTTON_13: return "Button 13";
case AKEYCODE_BUTTON_14: return "Button 14";
case AKEYCODE_BUTTON_15: return "Button 15";
case AKEYCODE_BUTTON_16: return "Button 16";
default: return base_type::get_button_name(button);
}
}

View File

@ -0,0 +1,41 @@
#pragma once
#include "input/api/Android/AndroidControllerProvider.h"
#include "input/api/Controller.h"
#include "input/motion/MotionSample.h"
#include "ControllerManager.h"
class AndroidController : public Controller<AndroidControllerProvider>
{
public:
AndroidController(std::string_view device_descriptor, std::string_view device_name);
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::Android) == "Android");
return to_string(InputAPI::Android);
}
InputAPI::Type api() const override
{
return InputAPI::Android;
}
bool is_connected() override;
bool has_motion() override;
MotionSample get_motion_sample() override;
bool has_rumble() override;
void start_rumble() override;
void stop_rumble() override;
std::string get_button_name(uint64 button) const override;
ControllerState raw_state() override;
private:
std::shared_ptr<ControllerManager::ControllerRuntimeState> m_state;
};

View File

@ -0,0 +1,18 @@
#include "input/api/Android/AndroidControllerProvider.h"
#include "ControllerManager.h"
#include "AndroidController.h"
std::vector<std::shared_ptr<ControllerBase>> AndroidControllerProvider::get_controllers()
{
std::vector<std::shared_ptr<ControllerBase>> result;
auto controllers = ControllerManager::instance().get_controllers();
result.reserve(controllers.size());
for (const auto& controller : controllers)
{
result.push_back(std::make_shared<AndroidController>(controller.descriptor, controller.name));
}
return result;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "input/api/ControllerProvider.h"
class AndroidControllerProvider : public ControllerProviderBase
{
public:
inline static InputAPI::Type kAPIType = InputAPI::Android;
InputAPI::Type api() const override
{
return kAPIType;
}
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
};

View File

@ -0,0 +1,192 @@
#include "ControllerManager.h"
#include "api/Controller.h"
ControllerManager& ControllerManager::instance()
{
static ControllerManager instance;
return instance;
}
void ControllerManager::vibrate_controller(std::string_view deviceDescriptor, sint64 milliseconds, sint32 amplitude)
{
std::scoped_lock lock{m_mutex};
if (!m_controllerCallbacks)
{
return;
}
m_controllerCallbacks->vibrate_controller(deviceDescriptor, milliseconds, amplitude);
}
void ControllerManager::cancel_controller_vibration(std::string_view deviceDescriptor)
{
std::scoped_lock lock{m_mutex};
if (!m_controllerCallbacks)
{
return;
}
m_controllerCallbacks->cancel_controller_vibration(deviceDescriptor);
}
void ControllerManager::set_controller_callbacks(std::shared_ptr<ControllerCallbacks> callbacks)
{
std::scoped_lock lock{m_mutex};
m_controllerCallbacks = std::move(callbacks);
}
void ControllerManager::set_controllers(const std::vector<ControllerInfo>& controllers)
{
std::scoped_lock lock{m_mutex};
for (auto& [descriptor, controller] : m_states)
{
std::scoped_lock stateLock{controller->mutex};
controller->isConnected = false;
}
for (const auto& ctrl : controllers)
{
auto it = m_states.find(ctrl.descriptor);
if (it != m_states.end())
{
auto statePtr = it->second;
auto& state = *statePtr;
std::scoped_lock stateLock{state.mutex};
state.name = ctrl.name;
state.hasRumble = ctrl.hasRumble;
state.hasMotion = ctrl.hasMotion;
state.isConnected = true;
}
}
m_controllers = controllers;
}
std::vector<ControllerManager::ControllerInfo> ControllerManager::get_controllers()
{
std::scoped_lock lock{m_mutex};
return m_controllers;
}
std::shared_ptr<ControllerManager::ControllerRuntimeState> ControllerManager::get_state(const std::string& deviceDescriptor)
{
std::scoped_lock lock{m_mutex};
auto it = m_states.find(deviceDescriptor);
if (it != m_states.end())
{
return it->second;
}
auto state = std::make_shared<ControllerRuntimeState>();
m_states[deviceDescriptor] = state;
return state;
}
void ControllerManager::process_key_event(const std::string& deviceDescriptor, int nativeKeyCode, bool isPressed)
{
update_state(deviceDescriptor, [&](auto& state) {
state.inputs.buttons.SetButtonState(nativeKeyCode, isPressed);
});
}
void ControllerManager::process_axis_event(const std::string& deviceDescriptor, int nativeAxisCode, float value)
{
update_state(deviceDescriptor, [&](auto& state) {
auto& controllerState = state.inputs;
switch (nativeAxisCode)
{
case AMOTION_EVENT_AXIS_X:
controllerState.axis.x = value;
break;
case AMOTION_EVENT_AXIS_Y:
controllerState.axis.y = value;
break;
case AMOTION_EVENT_AXIS_RX:
case AMOTION_EVENT_AXIS_Z:
controllerState.rotation.x = value;
break;
case AMOTION_EVENT_AXIS_RY:
case AMOTION_EVENT_AXIS_RZ:
controllerState.rotation.y = value;
break;
case AMOTION_EVENT_AXIS_LTRIGGER:
controllerState.trigger.x = value;
break;
case AMOTION_EVENT_AXIS_RTRIGGER:
controllerState.trigger.y = value;
break;
case AMOTION_EVENT_AXIS_HAT_X:
if (value == 0.0f)
{
controllerState.buttons.SetButtonState(kButtonRight, false);
controllerState.buttons.SetButtonState(kButtonLeft, false);
return;
}
else if (value > 0.0f)
controllerState.buttons.SetButtonState(kButtonRight, true);
else
controllerState.buttons.SetButtonState(kButtonLeft, true);
break;
case AMOTION_EVENT_AXIS_HAT_Y:
if (value == 0.0f)
{
controllerState.buttons.SetButtonState(kButtonUp, false);
controllerState.buttons.SetButtonState(kButtonDown, false);
return;
}
else if (value > 0.0f)
controllerState.buttons.SetButtonState(kButtonDown, true);
else
controllerState.buttons.SetButtonState(kButtonUp, true);
break;
default:
break;
}
});
}
void ControllerManager::process_motion(
const std::string& deviceDescriptor,
std::chrono::steady_clock::time_point timestamp,
float gyroX, float gyroY, float gyroZ,
float accelX, float accelY, float accelZ)
{
update_state(deviceDescriptor, [&](auto& state) {
using namespace std::chrono_literals;
const auto dif = timestamp - state.lastMotionTimestamp;
state.lastMotionTimestamp = timestamp;
if (dif <= 0ms)
{
return;
}
if (dif >= 10s)
{
return;
}
constexpr auto maxDuration = 1s;
auto clamped = std::min<std::common_type_t<decltype(dif), decltype(maxDuration)>>(dif, maxDuration);
float deltaSeconds = std::chrono::duration_cast<std::chrono::duration<float>>(dif).count();
state.motionHandler.processMotionSample(
deltaSeconds,
gyroX, gyroY, gyroZ,
accelX, accelY, accelZ);
});
}

View File

@ -0,0 +1,97 @@
#pragma once
#include "input/api/ControllerState.h"
#include "input/motion/MotionHandler.h"
#include <android/input.h>
class ControllerManager
{
public:
struct ControllerRuntimeState
{
bool isConnected{};
std::string name{};
ControllerState inputs{};
bool hasRumble{};
bool hasMotion{};
std::chrono::steady_clock::time_point lastMotionTimestamp{};
WiiUMotionHandler motionHandler{};
std::shared_mutex mutex{};
};
struct ControllerInfo
{
sint32 id{};
std::string descriptor{};
std::string name{};
bool hasRumble{};
bool hasMotion{};
};
class ControllerCallbacks
{
public:
virtual void vibrate_controller(std::string_view deviceDescriptor, sint64 milliseconds, sint32 amplitude) = 0;
virtual void cancel_controller_vibration(std::string_view deviceDescriptor) = 0;
};
static ControllerManager& instance();
void vibrate_controller(std::string_view deviceDescriptor, sint64 milliseconds, sint32 amplitude);
void cancel_controller_vibration(std::string_view deviceDescriptor);
void set_controller_callbacks(std::shared_ptr<ControllerCallbacks> callbacks);
void set_controllers(const std::vector<ControllerInfo>& controllers);
std::vector<ControllerInfo> get_controllers();
std::shared_ptr<ControllerRuntimeState> get_state(const std::string& deviceDescriptor);
void process_key_event(const std::string& deviceDescriptor, int nativeKeyCode, bool isPressed);
void process_axis_event(const std::string& deviceDescriptor, int nativeAxisCode, float value);
void process_motion(
const std::string& deviceDescriptor,
std::chrono::steady_clock::time_point timestamp,
float gyroX,
float gyroY,
float gyroZ,
float accelX,
float accelY,
float accelZ);
private:
ControllerManager() = default;
inline void update_state(const std::string& deviceDescriptor, std::invocable<ControllerRuntimeState&> auto f)
{
std::scoped_lock lock{m_mutex};
auto it = m_states.find(deviceDescriptor);
if (it == m_states.end())
{
return;
}
auto statePtr = it->second;
auto& state = *statePtr;
std::scoped_lock stateLock{state.mutex};
f(state);
}
std::mutex m_mutex;
std::shared_ptr<ControllerCallbacks> m_controllerCallbacks;
std::vector<ControllerInfo> m_controllers;
std::unordered_map<std::string, std::shared_ptr<ControllerRuntimeState>> m_states;
};

View File

@ -0,0 +1,95 @@
#include "DeviceController.h"
DeviceController::DeviceController() : base_type("device", "device")
{
}
void DeviceController::set_callbacks(std::shared_ptr<ControllerCallbacks> callbacks)
{
std::scoped_lock lock(s_mutex);
s_callbacks = std::move(callbacks);
}
bool DeviceController::is_connected()
{
return true;
}
bool DeviceController::has_rumble()
{
return true;
}
bool DeviceController::has_motion()
{
return true;
}
MotionSample DeviceController::get_motion_sample()
{
std::scoped_lock lock{s_mutex};
return s_motion_handler.getMotionSample();
}
ControllerState DeviceController::raw_state()
{
std::scoped_lock lock{s_mutex};
return s_raw_state;
}
void DeviceController::start_rumble()
{
std::scoped_lock lock{s_mutex};
if (m_settings.rumble <= 0 || m_settings.rumble > 1 || !s_callbacks)
{
return;
}
s_callbacks->start_rumble(5000, m_settings.rumble);
}
void DeviceController::stop_rumble()
{
std::scoped_lock lock{s_mutex};
if (s_callbacks)
{
s_callbacks->stop_rumble();
}
}
void DeviceController::process_motion(std::chrono::steady_clock::time_point timestamp, float gx, float gy, float gz, float accx, float accy, float accz)
{
using namespace std::chrono_literals;
std::scoped_lock lock{s_mutex};
auto dif = timestamp - s_last_motion_timestamp;
s_last_motion_timestamp = timestamp;
if (dif <= 0ms)
{
return;
}
if (dif >= 10s)
{
return;
}
constexpr auto maxDuration = 1s;
auto clamped = std::min<std::common_type_t<decltype(dif), decltype(maxDuration)>>(dif, maxDuration);
float deltaSeconds = std::chrono::duration_cast<std::chrono::duration<float>>(dif).count();
s_motion_handler.processMotionSample(
deltaSeconds,
gx, gy, gz,
accx, accy, accz);
}
void DeviceController::set_motion_enabled(bool enabled)
{
std::scoped_lock lock{s_mutex};
s_has_motion = enabled;
}

View File

@ -0,0 +1,57 @@
#pragma once
#include "input/api/InputAPI.h"
#include "input/api/Controller.h"
#include "DeviceControllerProvider.h"
class DeviceController : public Controller<DeviceControllerProvider>
{
public:
DeviceController();
class ControllerCallbacks
{
public:
virtual void start_rumble(sint64 duration, float rumble) = 0;
virtual void stop_rumble() = 0;
};
std::string_view api_name() const override
{
static_assert(to_string(InputAPI::Device) == "Device");
return to_string(InputAPI::Device);
}
InputAPI::Type api() const override
{
return InputAPI::Device;
}
static void set_callbacks(std::shared_ptr<ControllerCallbacks> callbacks);
bool is_connected() override;
bool has_rumble() override;
bool has_motion() override;
MotionSample get_motion_sample() override;
ControllerState raw_state() override;
void start_rumble() override;
void stop_rumble() override;
static void process_motion(std::chrono::steady_clock::time_point timestamp, float gx, float gy, float gz, float accx, float accy, float accz);
static void set_motion_enabled(bool enabled);
private:
inline static bool s_has_motion{};
inline static std::mutex s_mutex{};
inline static WiiUMotionHandler s_motion_handler{};
inline static std::chrono::steady_clock::time_point s_last_motion_timestamp{};
inline static std::shared_ptr<ControllerCallbacks> s_callbacks{};
inline static ControllerState s_raw_state{};
};

View File

@ -0,0 +1,7 @@
#include "DeviceControllerProvider.h"
#include "DeviceController.h"
std::vector<std::shared_ptr<ControllerBase>> DeviceControllerProvider::get_controllers()
{
return {std::make_shared<DeviceController>()};
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "input/api/ControllerProvider.h"
class DeviceControllerProvider : public ControllerProviderBase
{
public:
inline static InputAPI::Type kAPIType = InputAPI::Device;
InputAPI::Type api() const override
{
return kAPIType;
}
std::vector<std::shared_ptr<ControllerBase>> get_controllers() override;
};

View File

@ -14,6 +14,10 @@ namespace InputAPI
GameCube,
Wiimote,
Android,
Device,
WGIGamepad,
WGIRawController,
@ -42,6 +46,10 @@ namespace InputAPI
return "WGIRawController";
case SDLController:
return "SDLController";
case Android:
return "Android";
case Device:
return "Device";
default:
break;
}
@ -65,6 +73,10 @@ namespace InputAPI
return DSUClient;
else if (str == to_string(SDLController))
return SDLController;
else if (str == to_string(Android))
return Android;
else if (str == to_string(Device))
return Device;
else if (str == "DSU") // legacy
return DSUClient;