From 50ecad547ea7e88301583f17c9f1eea2cc75b0af Mon Sep 17 00:00:00 2001 From: t895 Date: Fri, 16 Feb 2024 21:19:17 -0500 Subject: android: Input mapping --- src/input_common/drivers/android.cpp | 324 +++++++++++++++++++++++++++++++++-- src/input_common/drivers/android.h | 123 ++++++++++--- 2 files changed, 416 insertions(+), 31 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/android.cpp b/src/input_common/drivers/android.cpp index b6a03fdc0..e859cc538 100644 --- a/src/input_common/drivers/android.cpp +++ b/src/input_common/drivers/android.cpp @@ -1,30 +1,47 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later +#include +#include +#include +#include "common/android/android_common.h" +#include "common/android/id_cache.h" #include "input_common/drivers/android.h" namespace InputCommon { Android::Android(std::string input_engine_) : InputEngine(std::move(input_engine_)) {} -void Android::RegisterController(std::size_t controller_number) { - PreSetController(GetIdentifier(controller_number)); +void Android::RegisterController(jobject j_input_device) { + auto env = Common::Android::GetEnvForThread(); + const std::string guid = Common::Android::GetJString( + env, static_cast( + env->CallObjectMethod(j_input_device, Common::Android::GetYuzuDeviceGetGUID()))); + const s32 port = env->CallIntMethod(j_input_device, Common::Android::GetYuzuDeviceGetPort()); + const auto identifier = GetIdentifier(guid, static_cast(port)); + PreSetController(identifier); + + if (input_devices.find(identifier) != input_devices.end()) { + env->DeleteGlobalRef(input_devices[identifier]); + } + auto new_device = env->NewGlobalRef(j_input_device); + input_devices[identifier] = new_device; } -void Android::SetButtonState(std::size_t controller_number, int button_id, bool value) { - const auto identifier = GetIdentifier(controller_number); +void Android::SetButtonState(std::string guid, size_t port, int button_id, bool value) { + const auto identifier = GetIdentifier(guid, port); SetButton(identifier, button_id, value); } -void Android::SetAxisState(std::size_t controller_number, int axis_id, float value) { - const auto identifier = GetIdentifier(controller_number); +void Android::SetAxisPosition(std::string guid, size_t port, int axis_id, float value) { + const auto identifier = GetIdentifier(guid, port); SetAxis(identifier, axis_id, value); } -void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x, +void Android::SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z) { - const auto identifier = GetIdentifier(controller_number); + const auto identifier = GetIdentifier(guid, port); const BasicMotion motion_data{ .gyro_x = gyro_x, .gyro_y = gyro_y, @@ -37,10 +54,295 @@ void Android::SetMotionState(std::size_t controller_number, u64 delta_timestamp, SetMotion(identifier, 0, motion_data); } -PadIdentifier Android::GetIdentifier(std::size_t controller_number) const { +Common::Input::DriverResult Android::SetVibration( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { + auto device = input_devices.find(identifier); + if (device != input_devices.end()) { + Common::Android::RunJNIOnFiber([&](JNIEnv* env) { + float average_intensity = + static_cast((vibration.high_amplitude + vibration.low_amplitude) / 2.0); + env->CallVoidMethod(device->second, Common::Android::GetYuzuDeviceVibrate(), + average_intensity); + }); + return Common::Input::DriverResult::Success; + } + return Common::Input::DriverResult::NotSupported; +} + +bool Android::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { + auto device = input_devices.find(identifier); + if (device != input_devices.end()) { + return Common::Android::RunJNIOnFiber([&](JNIEnv* env) { + return static_cast(env->CallBooleanMethod( + device->second, Common::Android::GetYuzuDeviceGetSupportsVibration())); + }); + } + return false; +} + +std::vector Android::GetInputDevices() const { + std::vector devices; + auto env = Common::Android::GetEnvForThread(); + for (const auto& [key, value] : input_devices) { + auto name_object = static_cast( + env->CallObjectMethod(value, Common::Android::GetYuzuDeviceGetName())); + const std::string name = + fmt::format("{} {}", Common::Android::GetJString(env, name_object), key.port); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", key.guid.RawString()}, + {"port", std::to_string(key.port)}, + }); + } + return devices; +} + +std::set Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const { + auto j_axes = static_cast( + env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceGetAxes())); + std::set axes; + for (int i = 0; i < env->GetArrayLength(j_axes); ++i) { + jobject axis = env->GetObjectArrayElement(j_axes, i); + axes.insert(env->GetIntField(axis, Common::Android::GetIntegerValueField())); + } + return axes; +} + +Common::ParamPackage Android::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y) const { + Common::ParamPackage params; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast(identifier.port)); + params.Set("guid", identifier.guid.RawString()); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); + params.Set("offset_x", 0); + params.Set("offset_y", 0); + params.Set("invert_x", "+"); + + // Invert Y-Axis by default + params.Set("invert_y", "-"); + return params; +} + +Common::ParamPackage Android::BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis, + bool invert) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast(identifier.port)); + params.Set("guid", identifier.guid.RawString()); + params.Set("axis", axis); + params.Set("threshold", "0.5"); + params.Set("invert", invert ? "-" : "+"); + return params; +} + +Common::ParamPackage Android::BuildButtonParamPackageForButton(PadIdentifier identifier, + s32 button) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast(identifier.port)); + params.Set("guid", identifier.guid.RawString()); + params.Set("button", button); + return params; +} + +bool Android::MatchVID(Common::UUID device, const std::vector& vids) const { + for (size_t i = 0; i < vids.size(); ++i) { + auto fucker = device.RawString(); + if (fucker.find(vids[i]) != std::string::npos) { + return true; + } + } + return false; +} + +AnalogMapping Android::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + + auto identifier = + GetIdentifier(params.Get("guid", ""), static_cast(params.Get("port", 0))); + auto& j_device = input_devices[identifier]; + if (j_device == nullptr) { + return {}; + } + + auto env = Common::Android::GetEnvForThread(); + std::set axes = GetDeviceAxes(env, j_device); + if (axes.size() == 0) { + return {}; + } + + AnalogMapping mapping = {}; + if (axes.find(AXIS_X) != axes.end() && axes.find(AXIS_Y) != axes.end()) { + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(identifier, AXIS_X, AXIS_Y)); + } + + if (axes.find(AXIS_RX) != axes.end() && axes.find(AXIS_RY) != axes.end()) { + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(identifier, AXIS_RX, AXIS_RY)); + } else if (axes.find(AXIS_Z) != axes.end() && axes.find(AXIS_RZ) != axes.end()) { + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(identifier, AXIS_Z, AXIS_RZ)); + } + return mapping; +} + +ButtonMapping Android::GetButtonMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + + auto identifier = + GetIdentifier(params.Get("guid", ""), static_cast(params.Get("port", 0))); + auto& j_device = input_devices[identifier]; + if (j_device == nullptr) { + return {}; + } + + auto env = Common::Android::GetEnvForThread(); + jintArray j_keys = env->NewIntArray(static_cast(keycode_ids.size())); + env->SetIntArrayRegion(j_keys, 0, static_cast(keycode_ids.size()), keycode_ids.data()); + auto j_has_keys_object = static_cast( + env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceHasKeys(), j_keys)); + jboolean isCopy = false; + jboolean* j_has_keys = env->GetBooleanArrayElements(j_has_keys_object, &isCopy); + + std::set available_keys; + for (size_t i = 0; i < keycode_ids.size(); ++i) { + if (j_has_keys[i]) { + available_keys.insert(keycode_ids[i]); + } + } + + // Some devices use axes instead of buttons for certain controls so we need all the axes here + std::set axes = GetDeviceAxes(env, j_device); + + ButtonMapping mapping = {}; + if (axes.find(AXIS_HAT_X) != axes.end() && axes.find(AXIS_HAT_Y) != axes.end()) { + mapping.insert_or_assign(Settings::NativeButton::DUp, + BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, true)); + mapping.insert_or_assign(Settings::NativeButton::DDown, + BuildAnalogParamPackageForButton(identifier, AXIS_HAT_Y, false)); + mapping.insert_or_assign(Settings::NativeButton::DLeft, + BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, true)); + mapping.insert_or_assign(Settings::NativeButton::DRight, + BuildAnalogParamPackageForButton(identifier, AXIS_HAT_X, false)); + } else if (available_keys.find(KEYCODE_DPAD_UP) != available_keys.end() && + available_keys.find(KEYCODE_DPAD_DOWN) != available_keys.end() && + available_keys.find(KEYCODE_DPAD_LEFT) != available_keys.end() && + available_keys.find(KEYCODE_DPAD_RIGHT) != available_keys.end()) { + mapping.insert_or_assign(Settings::NativeButton::DUp, + BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_UP)); + mapping.insert_or_assign(Settings::NativeButton::DDown, + BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_DOWN)); + mapping.insert_or_assign(Settings::NativeButton::DLeft, + BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_LEFT)); + mapping.insert_or_assign(Settings::NativeButton::DRight, + BuildButtonParamPackageForButton(identifier, KEYCODE_DPAD_RIGHT)); + } + + if (axes.find(AXIS_LTRIGGER) != axes.end()) { + mapping.insert_or_assign(Settings::NativeButton::ZL, BuildAnalogParamPackageForButton( + identifier, AXIS_LTRIGGER, false)); + } else if (available_keys.find(KEYCODE_BUTTON_L2) != available_keys.end()) { + mapping.insert_or_assign(Settings::NativeButton::ZL, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L2)); + } + + if (axes.find(AXIS_RTRIGGER) != axes.end()) { + mapping.insert_or_assign(Settings::NativeButton::ZR, BuildAnalogParamPackageForButton( + identifier, AXIS_RTRIGGER, false)); + } else if (available_keys.find(KEYCODE_BUTTON_R2) != available_keys.end()) { + mapping.insert_or_assign(Settings::NativeButton::ZR, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R2)); + } + + if (available_keys.find(KEYCODE_BUTTON_A) != available_keys.end()) { + if (MatchVID(identifier.guid, flipped_ab_vids)) { + mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_A)); + } else { + mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_A)); + } + } + if (available_keys.find(KEYCODE_BUTTON_B) != available_keys.end()) { + if (MatchVID(identifier.guid, flipped_ab_vids)) { + mapping.insert_or_assign(Settings::NativeButton::A, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_B)); + } else { + mapping.insert_or_assign(Settings::NativeButton::B, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_B)); + } + } + if (available_keys.find(KEYCODE_BUTTON_X) != available_keys.end()) { + if (MatchVID(identifier.guid, flipped_xy_vids)) { + mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_X)); + } else { + mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_X)); + } + } + if (available_keys.find(KEYCODE_BUTTON_Y) != available_keys.end()) { + if (MatchVID(identifier.guid, flipped_xy_vids)) { + mapping.insert_or_assign(Settings::NativeButton::X, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_Y)); + } else { + mapping.insert_or_assign(Settings::NativeButton::Y, BuildButtonParamPackageForButton( + identifier, KEYCODE_BUTTON_Y)); + } + } + + if (available_keys.find(KEYCODE_BUTTON_L1) != available_keys.end()) { + mapping.insert_or_assign(Settings::NativeButton::L, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_L1)); + } + if (available_keys.find(KEYCODE_BUTTON_R1) != available_keys.end()) { + mapping.insert_or_assign(Settings::NativeButton::R, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_R1)); + } + + if (available_keys.find(KEYCODE_BUTTON_THUMBL) != available_keys.end()) { + mapping.insert_or_assign( + Settings::NativeButton::LStick, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBL)); + } + if (available_keys.find(KEYCODE_BUTTON_THUMBR) != available_keys.end()) { + mapping.insert_or_assign( + Settings::NativeButton::RStick, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_THUMBR)); + } + + if (available_keys.find(KEYCODE_BUTTON_START) != available_keys.end()) { + mapping.insert_or_assign( + Settings::NativeButton::Plus, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_START)); + } + if (available_keys.find(KEYCODE_BUTTON_SELECT) != available_keys.end()) { + mapping.insert_or_assign( + Settings::NativeButton::Minus, + BuildButtonParamPackageForButton(identifier, KEYCODE_BUTTON_SELECT)); + } + + return mapping; +} + +Common::Input::ButtonNames Android::GetUIName( + [[maybe_unused]] const Common::ParamPackage& params) const { + return Common::Input::ButtonNames::Value; +} + +PadIdentifier Android::GetIdentifier(const std::string& guid, size_t port) const { return { - .guid = Common::UUID{}, - .port = controller_number, + .guid = Common::UUID{guid}, + .port = port, .pad = 0, }; } diff --git a/src/input_common/drivers/android.h b/src/input_common/drivers/android.h index 3f01817f6..ac60e3598 100644 --- a/src/input_common/drivers/android.h +++ b/src/input_common/drivers/android.h @@ -3,6 +3,8 @@ #pragma once +#include +#include #include "input_common/input_engine.h" namespace InputCommon { @@ -15,40 +17,121 @@ public: explicit Android(std::string input_engine_); /** - * Registers controller number to accept new inputs - * @param controller_number the controller number that will take this action + * Registers controller number to accept new inputs. + * @param j_input_device YuzuInputDevice object from the Android frontend to register. */ - void RegisterController(std::size_t controller_number); + void RegisterController(jobject j_input_device); /** - * Sets the status of all buttons bound with the key to pressed - * @param controller_number the controller number that will take this action - * @param button_id the id of the button - * @param value indicates if the button is pressed or not + * Sets the status of a button on a specific controller. + * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. + * @param port Port determined by controller connection order. + * @param button_id The Android Keycode corresponding to this event. + * @param value Whether the button is pressed or not. */ - void SetButtonState(std::size_t controller_number, int button_id, bool value); + void SetButtonState(std::string guid, size_t port, int button_id, bool value); /** - * Sets the status of a analog input to a specific player index - * @param controller_number the controller number that will take this action - * @param axis_id the id of the axis to move - * @param value the analog position of the axis + * Sets the status of an axis on a specific controller. + * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. + * @param port Port determined by controller connection order. + * @param axis_id The Android axis ID corresponding to this event. + * @param value Value along the given axis. */ - void SetAxisState(std::size_t controller_number, int axis_id, float value); + void SetAxisPosition(std::string guid, size_t port, int axis_id, float value); /** - * Sets the status of the motion sensor to a specific player index - * @param controller_number the controller number that will take this action - * @param delta_timestamp time passed since last reading - * @param gyro_x,gyro_y,gyro_z the gyro sensor readings - * @param accel_x,accel_y,accel_z the accelerometer reading + * Sets the status of the motion sensor on a specific controller + * @param guid 32 character hexadecimal string consisting of the controller's PID+VID. + * @param port Port determined by controller connection order. + * @param delta_timestamp Time passed since the last read. + * @param gyro_x,gyro_y,gyro_z Gyro sensor readings. + * @param accel_x,accel_y,accel_z Accelerometer sensor readings. */ - void SetMotionState(std::size_t controller_number, u64 delta_timestamp, float gyro_x, + void SetMotionState(std::string guid, size_t port, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z); + Common::Input::DriverResult SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + + bool IsVibrationEnabled(const PadIdentifier& identifier) override; + + std::vector GetInputDevices() const override; + + /** + * Gets the axes reported by the YuzuInputDevice. + * @param env JNI environment pointer. + * @param j_device YuzuInputDevice from the Android frontend. + * @return Set of the axes reported by the underlying Android InputDevice + */ + std::set GetDeviceAxes(JNIEnv* env, jobject& j_device) const; + + Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y) const; + + Common::ParamPackage BuildAnalogParamPackageForButton(PadIdentifier identifier, s32 axis, + bool invert) const; + + Common::ParamPackage BuildButtonParamPackageForButton(PadIdentifier identifier, + s32 button) const; + + bool MatchVID(Common::UUID device, const std::vector& vids) const; + + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + private: + std::unordered_map input_devices; + /// Returns the correct identifier corresponding to the player index - PadIdentifier GetIdentifier(std::size_t controller_number) const; + PadIdentifier GetIdentifier(const std::string& guid, size_t port) const; + + static constexpr s32 AXIS_X = 0; + static constexpr s32 AXIS_Y = 1; + static constexpr s32 AXIS_Z = 11; + static constexpr s32 AXIS_RX = 12; + static constexpr s32 AXIS_RY = 13; + static constexpr s32 AXIS_RZ = 14; + static constexpr s32 AXIS_HAT_X = 15; + static constexpr s32 AXIS_HAT_Y = 16; + static constexpr s32 AXIS_LTRIGGER = 17; + static constexpr s32 AXIS_RTRIGGER = 18; + + static constexpr s32 KEYCODE_DPAD_UP = 19; + static constexpr s32 KEYCODE_DPAD_DOWN = 20; + static constexpr s32 KEYCODE_DPAD_LEFT = 21; + static constexpr s32 KEYCODE_DPAD_RIGHT = 22; + static constexpr s32 KEYCODE_BUTTON_A = 96; + static constexpr s32 KEYCODE_BUTTON_B = 97; + static constexpr s32 KEYCODE_BUTTON_X = 99; + static constexpr s32 KEYCODE_BUTTON_Y = 100; + static constexpr s32 KEYCODE_BUTTON_L1 = 102; + static constexpr s32 KEYCODE_BUTTON_R1 = 103; + static constexpr s32 KEYCODE_BUTTON_L2 = 104; + static constexpr s32 KEYCODE_BUTTON_R2 = 105; + static constexpr s32 KEYCODE_BUTTON_THUMBL = 106; + static constexpr s32 KEYCODE_BUTTON_THUMBR = 107; + static constexpr s32 KEYCODE_BUTTON_START = 108; + static constexpr s32 KEYCODE_BUTTON_SELECT = 109; + const std::vector keycode_ids{ + KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT, + KEYCODE_BUTTON_A, KEYCODE_BUTTON_B, KEYCODE_BUTTON_X, KEYCODE_BUTTON_Y, + KEYCODE_BUTTON_L1, KEYCODE_BUTTON_R1, KEYCODE_BUTTON_L2, KEYCODE_BUTTON_R2, + KEYCODE_BUTTON_THUMBL, KEYCODE_BUTTON_THUMBR, KEYCODE_BUTTON_START, KEYCODE_BUTTON_SELECT, + }; + + const std::string sony_vid{"054c"}; + const std::string nintendo_vid{"057e"}; + const std::string razer_vid{"1532"}; + const std::string redmagic_vid{"3537"}; + const std::string backbone_labs_vid{"358a"}; + const std::vector flipped_ab_vids{sony_vid, nintendo_vid, razer_vid, redmagic_vid, + backbone_labs_vid}; + const std::vector flipped_xy_vids{sony_vid, razer_vid, redmagic_vid, + backbone_labs_vid}; }; } // namespace InputCommon -- cgit v1.2.3