| | |
| | |
| |
|
| | #include <set> |
| | #include <common/settings_input.h> |
| | #include <common/thread.h> |
| | #include <jni.h> |
| | #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_)) { |
| | vibration_thread = std::jthread([this](std::stop_token token) { |
| | Common::SetCurrentThreadName("Android_Vibration"); |
| | auto env = Common::Android::GetEnvForThread(); |
| | using namespace std::chrono_literals; |
| | while (!token.stop_requested()) { |
| | SendVibrations(env, token); |
| | } |
| | }); |
| | } |
| |
|
| | Android::~Android() = default; |
| |
|
| | void Android::RegisterController(jobject j_input_device) { |
| | auto env = Common::Android::GetEnvForThread(); |
| | const std::string guid = Common::Android::GetJString( |
| | env, static_cast<jstring>( |
| | 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<size_t>(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::string guid, size_t port, int button_id, bool value) { |
| | const auto identifier = GetIdentifier(guid, port); |
| | SetButton(identifier, button_id, value); |
| | } |
| |
|
| | 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::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(guid, port); |
| | const BasicMotion motion_data{ |
| | .gyro_x = gyro_x, |
| | .gyro_y = gyro_y, |
| | .gyro_z = gyro_z, |
| | .accel_x = accel_x, |
| | .accel_y = accel_y, |
| | .accel_z = accel_z, |
| | .delta_timestamp = delta_timestamp, |
| | }; |
| | SetMotion(identifier, 0, motion_data); |
| | } |
| |
|
| | Common::Input::DriverResult Android::SetVibration( |
| | [[maybe_unused]] const PadIdentifier& identifier, |
| | [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { |
| | vibration_queue.Push(VibrationRequest{ |
| | .identifier = identifier, |
| | .vibration = vibration, |
| | }); |
| | return Common::Input::DriverResult::Success; |
| | } |
| |
|
| | bool Android::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) { |
| | auto device = input_devices.find(identifier); |
| | if (device != input_devices.end()) { |
| | return Common::Android::RunJNIOnFiber<bool>([&](JNIEnv* env) { |
| | return static_cast<bool>(env->CallBooleanMethod( |
| | device->second, Common::Android::GetYuzuDeviceGetSupportsVibration())); |
| | }); |
| | } |
| | return false; |
| | } |
| |
|
| | std::vector<Common::ParamPackage> Android::GetInputDevices() const { |
| | std::vector<Common::ParamPackage> devices; |
| | auto env = Common::Android::GetEnvForThread(); |
| | for (const auto& [key, value] : input_devices) { |
| | auto name_object = static_cast<jstring>( |
| | 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<s32> Android::GetDeviceAxes(JNIEnv* env, jobject& j_device) const { |
| | auto j_axes = static_cast<jobjectArray>( |
| | env->CallObjectMethod(j_device, Common::Android::GetYuzuDeviceGetAxes())); |
| | std::set<s32> 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<int>(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", "+"); |
| |
|
| | |
| | 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<int>(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<int>(identifier.port)); |
| | params.Set("guid", identifier.guid.RawString()); |
| | params.Set("button", button); |
| | return params; |
| | } |
| |
|
| | bool Android::MatchVID(Common::UUID device, const std::vector<std::string>& 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<size_t>(params.Get("port", 0))); |
| | auto& j_device = input_devices[identifier]; |
| | if (j_device == nullptr) { |
| | return {}; |
| | } |
| |
|
| | auto env = Common::Android::GetEnvForThread(); |
| | std::set<s32> 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<size_t>(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<int>(keycode_ids.size())); |
| | env->SetIntArrayRegion(j_keys, 0, static_cast<int>(keycode_ids.size()), keycode_ids.data()); |
| | auto j_has_keys_object = static_cast<jbooleanArray>( |
| | 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<s32> available_keys; |
| | for (size_t i = 0; i < keycode_ids.size(); ++i) { |
| | if (j_has_keys[i]) { |
| | available_keys.insert(keycode_ids[i]); |
| | } |
| | } |
| |
|
| | |
| | std::set<s32> 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{guid}, |
| | .port = port, |
| | .pad = 0, |
| | }; |
| | } |
| |
|
| | void Android::SendVibrations(JNIEnv* env, std::stop_token token) { |
| | VibrationRequest request = vibration_queue.PopWait(token); |
| | auto device = input_devices.find(request.identifier); |
| | if (device != input_devices.end()) { |
| | float average_intensity = static_cast<float>( |
| | (request.vibration.high_amplitude + request.vibration.low_amplitude) / 2.0); |
| | env->CallVoidMethod(device->second, Common::Android::GetYuzuDeviceVibrate(), |
| | average_intensity); |
| | } |
| | } |
| |
|
| | } |
| |
|