From 5427867bee02622bb85b05ea29d771a382f38f3f Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Wed, 8 Apr 2026 01:09:27 -0400 Subject: [PATCH] [android] Refined physical controller automatic detection --- .../yuzu_emu/activities/EmulationActivity.kt | 11 +--- .../org/yuzu/yuzu_emu/utils/InputHandler.kt | 59 +++++++++++++++++-- 2 files changed, 58 insertions(+), 12 deletions(-) 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 d01bf81ce5..949edc1ee3 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 @@ -404,8 +404,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD && event.device?.isVirtual == false - val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK || - event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + val isControllerInput = InputHandler.isPhysicalGameController(event.device) if (!isControllerInput && event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE && @@ -426,8 +425,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { - val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK || - event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + val isControllerInput = InputHandler.isPhysicalGameController(event.device) if (!isControllerInput && event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD && @@ -461,10 +459,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager } private fun isGameController(deviceId: Int): Boolean { - val device = InputDevice.getDevice(deviceId) ?: return false - val sources = device.sources - return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || - sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK + return InputHandler.isPhysicalGameController(InputDevice.getDevice(deviceId)) } override fun onInputDeviceAdded(deviceId: Int) { 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 index 2c7356e6a7..a508346213 100644 --- 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 @@ -14,6 +14,60 @@ object InputHandler { var androidControllers = mapOf() var registeredControllers = mutableListOf() + private val controllerButtons = intArrayOf( + KeyEvent.KEYCODE_BUTTON_A, + KeyEvent.KEYCODE_BUTTON_B, + KeyEvent.KEYCODE_BUTTON_X, + KeyEvent.KEYCODE_BUTTON_Y, + KeyEvent.KEYCODE_BUTTON_L1, + KeyEvent.KEYCODE_BUTTON_R1, + KeyEvent.KEYCODE_BUTTON_L2, + KeyEvent.KEYCODE_BUTTON_R2, + KeyEvent.KEYCODE_BUTTON_THUMBL, + KeyEvent.KEYCODE_BUTTON_THUMBR, + KeyEvent.KEYCODE_BUTTON_START, + KeyEvent.KEYCODE_BUTTON_SELECT, + KeyEvent.KEYCODE_DPAD_UP, + KeyEvent.KEYCODE_DPAD_DOWN, + KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.KEYCODE_DPAD_RIGHT + ) + + private val controllerAxes = intArrayOf( + MotionEvent.AXIS_X, + MotionEvent.AXIS_Y, + MotionEvent.AXIS_Z, + MotionEvent.AXIS_RX, + MotionEvent.AXIS_RY, + MotionEvent.AXIS_RZ, + MotionEvent.AXIS_HAT_X, + MotionEvent.AXIS_HAT_Y, + MotionEvent.AXIS_LTRIGGER, + MotionEvent.AXIS_RTRIGGER + ) + + fun isPhysicalGameController(device: InputDevice?): Boolean { + device ?: return false + + if (device.isVirtual) { + return false + } + + val sources = device.sources + val hasControllerSource = + sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || + sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK + if (!hasControllerSource) { + return false + } + + val hasControllerButtons = device.hasKeys(*controllerButtons).any { it } + val hasControllerAxes = device.motionRanges.any { range -> + controllerAxes.contains(range.axis) + } + return hasControllerButtons || hasControllerAxes + } + fun dispatchKeyEvent(event: KeyEvent): Boolean { val action = when (event.action) { KeyEvent.ACTION_DOWN -> NativeInput.ButtonState.PRESSED @@ -57,10 +111,7 @@ object InputHandler { val inputSettings = NativeConfig.getInputSettings(true) deviceIds.forEach { deviceId -> InputDevice.getDevice(deviceId)?.apply { - // Verify that the device has gamepad buttons, control sticks, or both. - if (sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD || - sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK - ) { + if (isPhysicalGameController(this)) { if (!gameControllerDeviceIds.contains(controllerNumber)) { gameControllerDeviceIds[controllerNumber] = YuzuPhysicalDevice( this,