Merge branch 'libryujinx_bionic_L2R2_XBox' into 'libryujinx_bionic'

Fixed L2/R2 on XBox controllers

See merge request kenji-nx/ryujinx!10
This commit is contained in:
BeZide 2025-12-10 16:57:32 -06:00
commit cc00dc2967

View file

@ -4,10 +4,17 @@ import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import org.kenjinx.android.viewmodels.QuickSettings
import kotlin.math.abs
class PhysicalControllerManager(val activity: MainActivity) {
private var controllerId: Int = -1
// Trigger debouncing (analog → digital)
private var leftTriggerPressed = false
private var rightTriggerPressed = false
private val pressThreshold = 0.65f
private val releaseThreshold = 0.45f
fun onKeyEvent(event: KeyEvent): Boolean {
// Make sure we are connected
if (controllerId == -1) {
@ -17,40 +24,88 @@ class PhysicalControllerManager(val activity: MainActivity) {
val id = getGamePadButtonInputId(event.keyCode)
if (id != GamePadButtonInputId.None) {
val isNotFallback = (event.flags and KeyEvent.FLAG_FALLBACK) == 0
// Many gamepads send additional fallback events we suppress them.
if (isNotFallback) {
when (event.action) {
KeyEvent.ACTION_UP -> {
KenjinxNative.inputSetButtonReleased(id.ordinal, controllerId)
}
KeyEvent.ACTION_DOWN -> {
KenjinxNative.inputSetButtonPressed(id.ordinal, controllerId)
}
KeyEvent.ACTION_UP -> KenjinxNative.inputSetButtonReleased(id.ordinal, controllerId)
KeyEvent.ACTION_DOWN -> KenjinxNative.inputSetButtonPressed(id.ordinal, controllerId)
}
}
return true
}
return false
}
fun onMotionEvent(ev: MotionEvent) {
if (ev.action == MotionEvent.ACTION_MOVE) {
if (ev.action != MotionEvent.ACTION_MOVE) return
if (controllerId == -1) {
controllerId = KenjinxNative.inputConnectGamepad(0)
}
val leftStickX = ev.getAxisValue(MotionEvent.AXIS_X)
val leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y)
val rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z)
val rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ)
val device = ev.device
val source = InputDevice.SOURCE_JOYSTICK
fun hasAxis(axis: Int): Boolean =
device?.getMotionRange(axis, source) != null
fun axisValue(axis: Int): Float = ev.getAxisValue(axis)
// --- Sticks (prefer RX/RY on the right, fallback to Z/RZ) ---
val rightXaxis = if (hasAxis(MotionEvent.AXIS_RX)) MotionEvent.AXIS_RX else MotionEvent.AXIS_Z
val rightYaxis = if (hasAxis(MotionEvent.AXIS_RY)) MotionEvent.AXIS_RY else MotionEvent.AXIS_RZ
val leftStickX = if (hasAxis(MotionEvent.AXIS_X)) axisValue(MotionEvent.AXIS_X) else 0f
val leftStickY = if (hasAxis(MotionEvent.AXIS_Y)) axisValue(MotionEvent.AXIS_Y) else 0f
val rightStickX = if (hasAxis(rightXaxis)) axisValue(rightXaxis) else 0f
val rightStickY = if (hasAxis(rightYaxis)) axisValue(rightYaxis) else 0f
KenjinxNative.inputSetStickAxis(1, leftStickX, -leftStickY, controllerId)
KenjinxNative.inputSetStickAxis(2, rightStickX, -rightStickY, controllerId)
ev.device?.apply {
// --- Read triggers (with fallbacks) ---
// Preferred: LTRIGGER/RTRIGGER, then BRAKE/GAS.
// If the right stick uses RX/RY (standard on Xbox), Z/RZ are free → use as an additional fallback.
// If the stick uses Z/RZ, do NOT use them for triggers (to avoid conflicts).
val rightStickUsesZ = (rightXaxis == MotionEvent.AXIS_Z)
val rightStickUsesRZ = (rightYaxis == MotionEvent.AXIS_RZ)
val rawLT = when {
hasAxis(MotionEvent.AXIS_LTRIGGER) -> axisValue(MotionEvent.AXIS_LTRIGGER)
hasAxis(MotionEvent.AXIS_BRAKE) -> axisValue(MotionEvent.AXIS_BRAKE)
!rightStickUsesZ && hasAxis(MotionEvent.AXIS_Z) -> axisValue(MotionEvent.AXIS_Z)
else -> 0f
}
val rawRT = when {
hasAxis(MotionEvent.AXIS_RTRIGGER) -> axisValue(MotionEvent.AXIS_RTRIGGER)
hasAxis(MotionEvent.AXIS_GAS) -> axisValue(MotionEvent.AXIS_GAS)
!rightStickUsesRZ && hasAxis(MotionEvent.AXIS_RZ) -> axisValue(MotionEvent.AXIS_RZ)
else -> 0f
}
// Some pads report slight offsets — normalize
val lt = if (abs(rawLT) < 0.02f) 0f else rawLT.coerceIn(0f, 1f)
val rt = if (abs(rawRT) < 0.02f) 0f else rawRT.coerceIn(0f, 1f)
// Analog → digital with hysteresis
if (!leftTriggerPressed && lt >= pressThreshold) {
leftTriggerPressed = true
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.LeftTrigger.ordinal, controllerId)
} else if (leftTriggerPressed && lt <= releaseThreshold) {
leftTriggerPressed = false
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.LeftTrigger.ordinal, controllerId)
}
if (!rightTriggerPressed && rt >= pressThreshold) {
rightTriggerPressed = true
KenjinxNative.inputSetButtonPressed(GamePadButtonInputId.RightTrigger.ordinal, controllerId)
} else if (rightTriggerPressed && rt <= releaseThreshold) {
rightTriggerPressed = false
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.RightTrigger.ordinal, controllerId)
}
// --- DPAD as HAT (as before) ---
device?.apply {
if (sources and InputDevice.SOURCE_DPAD != InputDevice.SOURCE_DPAD) {
// Controller uses HAT instead of “real” DPAD
val dPadHor = ev.getAxisValue(MotionEvent.AXIS_HAT_X)
val dPadVert = ev.getAxisValue(MotionEvent.AXIS_HAT_Y)
@ -83,7 +138,6 @@ class PhysicalControllerManager(val activity: MainActivity) {
}
}
}
}
fun connect(): Int {
controllerId = KenjinxNative.inputConnectGamepad(0)
@ -92,30 +146,37 @@ class PhysicalControllerManager(val activity: MainActivity) {
fun disconnect() {
controllerId = -1
// If a trigger was "stuck" on disconnect, release it just in case
if (leftTriggerPressed) {
leftTriggerPressed = false
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.LeftTrigger.ordinal, controllerId)
}
if (rightTriggerPressed) {
rightTriggerPressed = false
KenjinxNative.inputSetButtonReleased(GamePadButtonInputId.RightTrigger.ordinal, controllerId)
}
}
private fun getGamePadButtonInputId(keycode: Int): GamePadButtonInputId {
val quickSettings = QuickSettings(activity)
return when (keycode) {
// ABXY (Switch/Xbox layout switchable)
// ABXY (Switch/Xbox layout)
KeyEvent.KEYCODE_BUTTON_A -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.A else GamePadButtonInputId.B
KeyEvent.KEYCODE_BUTTON_B -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.B else GamePadButtonInputId.A
KeyEvent.KEYCODE_BUTTON_X -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.X else GamePadButtonInputId.Y
KeyEvent.KEYCODE_BUTTON_Y -> if (!quickSettings.useSwitchLayout) GamePadButtonInputId.Y else GamePadButtonInputId.X
// Shoulder buttons
// Shoulder & Trigger (if a pad sends them as key events)
KeyEvent.KEYCODE_BUTTON_L1 -> GamePadButtonInputId.LeftShoulder
KeyEvent.KEYCODE_BUTTON_L2 -> GamePadButtonInputId.LeftTrigger
KeyEvent.KEYCODE_BUTTON_R1 -> GamePadButtonInputId.RightShoulder
KeyEvent.KEYCODE_BUTTON_R2 -> GamePadButtonInputId.RightTrigger
// **L3 / R3 (Stick-Click) CORRECT: *_Button**
// L3 / R3
KeyEvent.KEYCODE_BUTTON_THUMBL -> GamePadButtonInputId.LeftStickButton
KeyEvent.KEYCODE_BUTTON_THUMBR -> GamePadButtonInputId.RightStickButton
// Additional fallback keycodes for some pads (optional)
KeyEvent.KEYCODE_BUTTON_11 -> GamePadButtonInputId.LeftStickButton // isolated L3
KeyEvent.KEYCODE_BUTTON_12 -> GamePadButtonInputId.RightStickButton // isolated R3
KeyEvent.KEYCODE_BUTTON_11 -> GamePadButtonInputId.LeftStickButton
KeyEvent.KEYCODE_BUTTON_12 -> GamePadButtonInputId.RightStickButton
// D-Pad
KeyEvent.KEYCODE_DPAD_UP -> GamePadButtonInputId.DpadUp