// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include #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_)) { 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( 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::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([&](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{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( (request.vibration.high_amplitude + request.vibration.low_amplitude) / 2.0); env->CallVoidMethod(device->second, Common::Android::GetYuzuDeviceVibrate(), average_intensity); } } } // namespace InputCommon