From 6dfe4240acebe91ee466b970834e71f131d0b349 Mon Sep 17 00:00:00 2001 From: german77 Date: Tue, 4 Apr 2023 02:08:53 -0600 Subject: [PATCH] android: Implement gamepad input --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 42 +++ .../yuzu_emu/activities/EmulationActivity.kt | 38 ++- .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 16 +- .../org/yuzu/yuzu_emu/utils/InputHandler.kt | 323 ++++++++++++++++++ src/android/app/src/main/jni/native.cpp | 90 +++++ src/android/app/src/main/jni/native.h | 12 + 6 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index cd9bc9ef0..c11b6bc16 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -43,6 +43,21 @@ object NativeLibrary { const val Player8Device = 7 const val ConsoleDevice = 8 + /** + * Controller type for each device + */ + const val ProController = 3 + const val Handheld = 4 + const val JoyconDual = 5 + const val JoyconLeft = 6 + const val JoyconRight = 7 + const val GameCube = 8 + const val Pokeball = 9 + const val NES = 10 + const val SNES = 11 + const val N64 = 12 + const val SegaGenesis = 13 + @JvmField var sEmulationActivity = WeakReference(null) @@ -70,6 +85,33 @@ object NativeLibrary { } else getFileSize(appContext, path) } + /** + * Returns true if pro controller isn't available and handheld is + */ + external fun isHandheldOnly(): Boolean + + /** + * Changes controller type for a specific device. + * + * @param Device The input descriptor of the gamepad. + * @param Type The NpadStyleIndex of the gamepad. + */ + external fun setDeviceType(Device: Int, Type: Int): Boolean + + /** + * Handles event when a gamepad is connected. + * + * @param Device The input descriptor of the gamepad. + */ + external fun onGamePadConnectEvent(Device: Int): Boolean + + /** + * Handles event when a gamepad is disconnected. + * + * @param Device The input descriptor of the gamepad. + */ + external fun onGamePadDisconnectEvent(Device: Int): Boolean + /** * Handles button press events for a gamepad. * diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index fd174fd2d..4c57de067 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -8,9 +8,9 @@ import android.content.DialogInterface import android.content.Intent import android.graphics.Rect import android.os.Bundle +import android.view.* import android.view.KeyEvent -import android.view.View -import android.view.WindowManager +import android.view.MotionEvent import android.view.inputmethod.InputMethodManager import androidx.appcompat.app.AppCompatActivity import androidx.preference.PreferenceManager @@ -23,6 +23,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.fragments.EmulationFragment import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.utils.ControllerMappingHelper +import org.yuzu.yuzu_emu.utils.InputHandler import org.yuzu.yuzu_emu.utils.NfcReader import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable import org.yuzu.yuzu_emu.utils.ThemeHelper @@ -38,6 +39,7 @@ open class EmulationActivity : AppCompatActivity() { private var menuVisible = false private var emulationFragment: EmulationFragment? = null private lateinit var nfcReader: NfcReader + private lateinit var inputHandler: InputHandler private lateinit var game: Game @@ -80,6 +82,9 @@ open class EmulationActivity : AppCompatActivity() { nfcReader = NfcReader(this) nfcReader.initialize() + inputHandler = InputHandler() + inputHandler.initialize() + // Start a foreground service to prevent the app from getting killed in the background // TODO(bunnei): Disable notifications until we support app suspension. //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class); @@ -108,6 +113,7 @@ open class EmulationActivity : AppCompatActivity() { } return super.onKeyDown(keyCode, event) } + override fun onResume() { super.onResume() nfcReader.startScanning() @@ -129,6 +135,29 @@ open class EmulationActivity : AppCompatActivity() { super.onSaveInstanceState(outState) } + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + // Handling the case where the back button is pressed. + if (event.keyCode == KeyEvent.KEYCODE_BACK) { + onBackPressedDispatcher.onBackPressed() + return true + } + + return inputHandler.dispatchKeyEvent(event) + } + + override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { + if (event.source and InputDevice.SOURCE_CLASS_JOYSTICK === 0) { + return super.dispatchGenericMotionEvent(event) + } + + // Don't attempt to do anything if we are disconnecting a device. + if (event.actionMasked == MotionEvent.ACTION_CANCEL) { + return true + } + + return inputHandler.dispatchGenericMotionEvent(event) + } + private fun restoreState(savedInstanceState: Bundle) { game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! } @@ -159,8 +188,9 @@ open class EmulationActivity : AppCompatActivity() { private fun adjustScale() { val sliderBinding = DialogSliderBinding.inflate(layoutInflater) sliderBinding.slider.valueTo = 150F - sliderBinding.slider.value = PreferenceManager.getDefaultSharedPreferences(applicationContext) - .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() + sliderBinding.slider.value = + PreferenceManager.getDefaultSharedPreferences(applicationContext) + .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat() sliderBinding.slider.addOnChangeListener(OnChangeListener { _, value, _ -> sliderBinding.textValue.text = value.toString() setControlScale(value.toInt()) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index c0dc8ce76..c2adf0ec6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -113,13 +113,15 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context } var shouldUpdateView = false + val playerIndex = + if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device for (button in overlayButtons) { if (!button.updateStatus(event)) { continue } NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, button.buttonId, button.status ) @@ -131,22 +133,22 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context continue } NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, dpad.upId, dpad.upStatus ) NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, dpad.downId, dpad.downStatus ) NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, dpad.leftId, dpad.leftStatus ) NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, dpad.rightId, dpad.rightStatus ) @@ -159,13 +161,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : SurfaceView(context } val axisID = joystick.joystickId NativeLibrary.onGamePadJoystickEvent( - NativeLibrary.Player1Device, + playerIndex, axisID, joystick.xAxis, joystick.realYAxis ) NativeLibrary.onGamePadButtonEvent( - NativeLibrary.Player1Device, + playerIndex, joystick.buttonId, joystick.buttonStatus ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt new file mode 100644 index 000000000..a48b7e00a --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InputHandler.kt @@ -0,0 +1,323 @@ +package org.yuzu.yuzu_emu.utils + +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent +import org.yuzu.yuzu_emu.NativeLibrary + +class InputHandler { + fun initialize() { + // Connect first controller + NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device)); + } + + fun dispatchKeyEvent(event: KeyEvent): Boolean { + val button: Int = when (event.device.vendorId) { + 0x045E -> getInputXboxButtonKey(event.keyCode) + 0x054C -> getInputDS5ButtonKey(event.keyCode) + 0x057E -> getInputJoyconButtonKey(event.keyCode) + 0x1532 -> getInputRazerButtonKey(event.keyCode) + else -> getInputGenericButtonKey(event.keyCode) + } + + val action = when (event.action) { + KeyEvent.ACTION_DOWN -> NativeLibrary.ButtonState.PRESSED + KeyEvent.ACTION_UP -> NativeLibrary.ButtonState.RELEASED + else -> return false + } + + // Ignore invalid buttons + if (button < 0) { + return false + } + + return NativeLibrary.onGamePadButtonEvent( + getPlayerNumber(event.device.controllerNumber), + button, + action + ) + } + + fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { + val device = event.device + // Check every axis input available on the controller + for (range in device.motionRanges) { + val axis = range.axis; + when (device.vendorId) { + 0x045E -> setGenericAxisInput(event, axis) + 0x054C -> setGenericAxisInput(event, axis) + 0x057E -> setJoyconAxisInput(event, axis) + 0x1532 -> setRazerAxisInput(event, axis) + else -> setGenericAxisInput(event, axis) + } + } + + return true + } + + private fun getPlayerNumber(index: Int): Int { + // TODO: Joycons are handled as different controllers. Find a way to merge them. + return when (index) { + 2 -> NativeLibrary.Player2Device + 3 -> NativeLibrary.Player3Device + 4 -> NativeLibrary.Player4Device + 5 -> NativeLibrary.Player5Device + 6 -> NativeLibrary.Player6Device + 7 -> NativeLibrary.Player7Device + 8 -> NativeLibrary.Player8Device + else -> if (NativeLibrary.isHandheldOnly()) NativeLibrary.ConsoleDevice else NativeLibrary.Player1Device + } + } + + private fun getAxisToButton(axis: Float): Int { + return if (axis > 0.5f) NativeLibrary.ButtonState.PRESSED else NativeLibrary.ButtonState.RELEASED + } + + private fun setAxisDpadState(playerNumber: Int, xAxis: Float, yAxis: Float) { + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.DPAD_UP, + getAxisToButton(-yAxis) + ) + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.DPAD_DOWN, + getAxisToButton(yAxis) + ) + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.DPAD_LEFT, + getAxisToButton(-xAxis) + ) + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.DPAD_RIGHT, + getAxisToButton(xAxis) + ) + } + + private fun getInputDS5ButtonKey(key: Int): Int { + // The missing ds5 buttons are axis + return when (key) { + KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B + KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A + KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y + KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X + KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L + KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R + KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L + KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R + KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS + KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS + else -> -1 + } + } + + private fun getInputJoyconButtonKey(key: Int): Int { + // Joycon support is half dead. A lot of buttons can't be mapped + return when (key) { + KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B + KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A + KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X + KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y + KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP + KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN + KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT + KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT + KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L + KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R + KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL + KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR + KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L + KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R + KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS + KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS + else -> -1 + } + } + + private fun getInputXboxButtonKey(key: Int): Int { + // The missing xbox buttons are axis + return when (key) { + KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A + KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B + KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X + KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y + KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L + KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R + KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L + KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R + KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS + KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS + else -> -1 + } + } + + private fun getInputRazerButtonKey(key: Int): Int { + // The missing xbox buttons are axis + return when (key) { + KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_B + KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_A + KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_Y + KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_X + KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L + KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R + KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L + KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R + KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS + KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS + else -> -1 + } + } + + private fun getInputGenericButtonKey(key: Int): Int { + return when (key) { + KeyEvent.KEYCODE_BUTTON_A -> NativeLibrary.ButtonType.BUTTON_A + KeyEvent.KEYCODE_BUTTON_B -> NativeLibrary.ButtonType.BUTTON_B + KeyEvent.KEYCODE_BUTTON_X -> NativeLibrary.ButtonType.BUTTON_X + KeyEvent.KEYCODE_BUTTON_Y -> NativeLibrary.ButtonType.BUTTON_Y + KeyEvent.KEYCODE_DPAD_UP -> NativeLibrary.ButtonType.DPAD_UP + KeyEvent.KEYCODE_DPAD_DOWN -> NativeLibrary.ButtonType.DPAD_DOWN + KeyEvent.KEYCODE_DPAD_LEFT -> NativeLibrary.ButtonType.DPAD_LEFT + KeyEvent.KEYCODE_DPAD_RIGHT -> NativeLibrary.ButtonType.DPAD_RIGHT + KeyEvent.KEYCODE_BUTTON_L1 -> NativeLibrary.ButtonType.TRIGGER_L + KeyEvent.KEYCODE_BUTTON_R1 -> NativeLibrary.ButtonType.TRIGGER_R + KeyEvent.KEYCODE_BUTTON_L2 -> NativeLibrary.ButtonType.TRIGGER_ZL + KeyEvent.KEYCODE_BUTTON_R2 -> NativeLibrary.ButtonType.TRIGGER_ZR + KeyEvent.KEYCODE_BUTTON_THUMBL -> NativeLibrary.ButtonType.STICK_L + KeyEvent.KEYCODE_BUTTON_THUMBR -> NativeLibrary.ButtonType.STICK_R + KeyEvent.KEYCODE_BUTTON_START -> NativeLibrary.ButtonType.BUTTON_PLUS + KeyEvent.KEYCODE_BUTTON_SELECT -> NativeLibrary.ButtonType.BUTTON_MINUS + else -> -1 + } + } + + private fun setGenericAxisInput(event: MotionEvent, axis: Int) { + val playerNumber = getPlayerNumber(event.device.controllerNumber) + + when (axis) { + MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_L, + event.getAxisValue(MotionEvent.AXIS_X), + -event.getAxisValue(MotionEvent.AXIS_Y) + ) + MotionEvent.AXIS_RX, MotionEvent.AXIS_RY -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_R, + event.getAxisValue(MotionEvent.AXIS_RX), + -event.getAxisValue(MotionEvent.AXIS_RY) + ) + MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_R, + event.getAxisValue(MotionEvent.AXIS_Z), + -event.getAxisValue(MotionEvent.AXIS_RZ) + ) + MotionEvent.AXIS_LTRIGGER -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZL, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_LTRIGGER)) + ) + MotionEvent.AXIS_BRAKE -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZL, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE)) + ) + MotionEvent.AXIS_RTRIGGER -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZR, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_RTRIGGER)) + ) + MotionEvent.AXIS_GAS -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZR, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS)) + ) + MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> + setAxisDpadState( + playerNumber, + event.getAxisValue(MotionEvent.AXIS_HAT_X), + event.getAxisValue(MotionEvent.AXIS_HAT_Y) + ) + } + } + + + private fun setJoyconAxisInput(event: MotionEvent, axis: Int) { + // Joycon support is half dead. Right joystick doesn't work + val playerNumber = getPlayerNumber(event.device.controllerNumber) + + when (axis) { + MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_L, + event.getAxisValue(MotionEvent.AXIS_X), + -event.getAxisValue(MotionEvent.AXIS_Y) + ) + MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_R, + event.getAxisValue(MotionEvent.AXIS_Z), + -event.getAxisValue(MotionEvent.AXIS_RZ) + ) + MotionEvent.AXIS_RX, MotionEvent.AXIS_RY -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_R, + event.getAxisValue(MotionEvent.AXIS_RX), + -event.getAxisValue(MotionEvent.AXIS_RY) + ) + } + } + + private fun setRazerAxisInput(event: MotionEvent, axis: Int) { + val playerNumber = getPlayerNumber(event.device.controllerNumber) + + when (axis) { + MotionEvent.AXIS_X, MotionEvent.AXIS_Y -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_L, + event.getAxisValue(MotionEvent.AXIS_X), + -event.getAxisValue(MotionEvent.AXIS_Y) + ) + MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ -> + NativeLibrary.onGamePadJoystickEvent( + playerNumber, + NativeLibrary.StickType.STICK_R, + event.getAxisValue(MotionEvent.AXIS_Z), + -event.getAxisValue(MotionEvent.AXIS_RZ) + ) + MotionEvent.AXIS_BRAKE -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZL, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_BRAKE)) + ) + MotionEvent.AXIS_GAS -> + NativeLibrary.onGamePadButtonEvent( + playerNumber, + NativeLibrary.ButtonType.TRIGGER_ZR, + getAxisToButton(event.getAxisValue(MotionEvent.AXIS_GAS)) + ) + MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y -> + setAxisDpadState( + playerNumber, + event.getAxisValue(MotionEvent.AXIS_HAT_X), + event.getAxisValue(MotionEvent.AXIS_HAT_Y) + ) + } + } + + +} \ No newline at end of file diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index b10c55a45..a7321af2a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -38,6 +38,8 @@ #include "core/frontend/applets/software_keyboard.h" #include "core/frontend/applets/web_browser.h" #include "core/hid/hid_core.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_types.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -274,6 +276,60 @@ public: m_rom_metadata_cache.clear(); } + bool IsHandheldOnly(){ + const auto npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); + + if (npad_style_set.fullkey == 1) { + return false; + } + + if (npad_style_set.handheld == 0) { + return false; + } + + return !Settings::values.use_docked_mode.GetValue(); + } + + void SetDeviceType(int index, int type){ + auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->SetNpadStyleIndex(static_cast(type)); + } + + void OnGamepadConnectEvent(int index){ + auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + + // Ensure that player1 is configured correctly and handheld disconnected + if(controller->GetNpadIdType() == Core::HID::NpadIdType::Player1){ + auto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld); + + if(controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { + handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); + handheld->Disconnect(); + } + } + + // Ensure that handheld is configured correctly and player 1 disconnected + if(controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld){ + auto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); + + if(controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { + player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); + player1->Disconnect(); + } + } + + if(!controller->IsConnected()){ + controller->Connect(); + } + } + + void OnGamepadDisconnectEvent(int index){ + auto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); + controller->Disconnect(); + } + SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { return m_software_keyboard; } @@ -440,11 +496,45 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning([[maybe_unused]] JNIEnv return static_cast(EmulationSession::GetInstance().IsRunning()); } +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz) { + return EmulationSession::GetInstance().IsHandheldOnly(); +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz, + jint j_device, + jint j_type) { + if (EmulationSession::GetInstance().IsRunning()) { + EmulationSession::GetInstance().SetDeviceType(j_device, j_type); + } + return static_cast(true); +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz, + jint j_device) { + if (EmulationSession::GetInstance().IsRunning()) { + EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); + } + return static_cast(true); +} + +jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent([[maybe_unused]] JNIEnv* env, + [[maybe_unused]] jclass clazz, + jint j_device) { + if (EmulationSession::GetInstance().IsRunning()) { + EmulationSession::GetInstance().OnGamepadDisconnectEvent(j_device); + } + return static_cast(true); +} jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadButtonEvent([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, [[maybe_unused]] jint j_device, jint j_button, jint action) { if (EmulationSession::GetInstance().IsRunning()) { + // Ensure gamepad is connected + EmulationSession::GetInstance().OnGamepadConnectEvent(j_device); EmulationSession::GetInstance().Window().OnGamepadButtonEvent(j_device, j_button, action != 0); } diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 8336e525a..d1e382a33 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -25,6 +25,18 @@ JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_ResetRomMetadata(JN JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_IsRunning(JNIEnv* env, jclass clazz); +JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly( + JNIEnv* env, jclass clazz); + +JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_setDeviceType( + JNIEnv* env, jclass clazz, jstring j_device, jstring j_type); + +JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadConnectEvent( + JNIEnv* env, jclass clazz, jstring j_device); + +JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadDisconnectEvent( + JNIEnv* env, jclass clazz, jstring j_device); + JNIEXPORT jboolean JNICALL Java_org_yuzu_yuzu_1emu_NativeLibrary_onGamePadEvent( JNIEnv* env, jclass clazz, jstring j_device, jint j_button, jint action);