Don't ignore HAT axes input events

Capture HAT axes events ourselves instead of relying on the android framework to turn them into KeyCodes. Fixes handling of DPAD button presses on most controllers.
This commit is contained in:
lynxnb 2022-11-03 01:27:23 +01:00 committed by Niccolò Betto
parent f1ec771944
commit 3a657c44cc
3 changed files with 33 additions and 55 deletions

View File

@ -44,6 +44,8 @@ data class MotionHostEvent(override val descriptor : String = "", val axis : Int
MotionEvent.AXIS_Y, MotionEvent.AXIS_Y,
MotionEvent.AXIS_Z, MotionEvent.AXIS_Z,
MotionEvent.AXIS_RZ, MotionEvent.AXIS_RZ,
MotionEvent.AXIS_HAT_X,
MotionEvent.AXIS_HAT_Y,
MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_LTRIGGER,
MotionEvent.AXIS_RTRIGGER, MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_THROTTLE, MotionEvent.AXIS_THROTTLE,

View File

@ -5,7 +5,6 @@
package emu.skyline.input package emu.skyline.input
import android.graphics.PointF
import android.view.InputDevice import android.view.InputDevice
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
@ -120,58 +119,47 @@ class InputHandler(private val inputManager : InputManager, private val preferen
*/ */
private val axesHistory = arrayOfNulls<Float>(MotionHostEvent.axes.size) private val axesHistory = arrayOfNulls<Float>(MotionHostEvent.axes.size)
/**
* The last value of the HAT axes so it can be ignored in [onGenericMotionEvent] so they are handled by [dispatchKeyEvent] instead
*/
private var oldHat = PointF()
/** /**
* Handles translating any [MotionHostEvent]s to a [GuestEvent] that is passed into libskyline * Handles translating any [MotionHostEvent]s to a [GuestEvent] that is passed into libskyline
*/ */
fun handleMotionEvent(event : MotionEvent) : Boolean { fun handleMotionEvent(event : MotionEvent) : Boolean {
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE) { if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE) {
val hat = PointF(event.getAxisValue(MotionEvent.AXIS_HAT_X), event.getAxisValue(MotionEvent.AXIS_HAT_Y)) for (axisItem in MotionHostEvent.axes.withIndex()) {
val axis = axisItem.value
var value = event.getAxisValue(axis)
if (hat == oldHat) { if ((event.historySize != 0 && value != event.getHistoricalAxisValue(axis, 0)) || (axesHistory[axisItem.index]?.let { it == value } == false)) {
for (axisItem in MotionHostEvent.axes.withIndex()) { var polarity = value > 0 || (value == 0f && axesHistory[axisItem.index]?.let { it >= 0 } == true)
val axis = axisItem.value
var value = event.getAxisValue(axis)
if ((event.historySize != 0 && value != event.getHistoricalAxisValue(axis, 0)) || (axesHistory[axisItem.index]?.let { it == value } == false)) { val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
var polarity = value > 0 || (value == 0f && axesHistory[axisItem.index]?.let { it >= 0 } == true) inputManager.eventMap[hostEvent] ?: if (value == 0f) {
polarity = false
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent -> inputManager.eventMap[hostEvent.copy(polarity = false)]
inputManager.eventMap[hostEvent] ?: if (value == 0f) { } else {
polarity = false null
inputManager.eventMap[hostEvent.copy(polarity = false)]
} else {
null
}
}
when (guestEvent) {
is ButtonGuestEvent -> {
if (guestEvent.button != ButtonId.Menu)
setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.state else ButtonState.Released.state)
}
is AxisGuestEvent -> {
value = guestEvent.value(value)
value = if (polarity) abs(value) else -abs(value)
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (value * Short.MAX_VALUE).toInt())
}
} }
} }
axesHistory[axisItem.index] = value when (guestEvent) {
is ButtonGuestEvent -> {
val action = if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.state else ButtonState.Released.state
if (guestEvent.button != ButtonId.Menu)
setButtonState(guestEvent.id, guestEvent.button.value(), action)
}
is AxisGuestEvent -> {
value = guestEvent.value(value)
value = if (polarity) abs(value) else -abs(value)
value = if (guestEvent.axis == AxisId.LX || guestEvent.axis == AxisId.RX) value else -value
setAxisValue(guestEvent.id, guestEvent.axis.ordinal, (value * Short.MAX_VALUE).toInt())
}
}
} }
return true axesHistory[axisItem.index] = value
} else {
oldHat = hat
} }
return true
} }
return false return false

View File

@ -101,10 +101,6 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
var axisRunnable : Runnable? = null // The Runnable that is used for counting down till an axis is selected var axisRunnable : Runnable? = null // The Runnable that is used for counting down till an axis is selected
val axisHandler = Handler(Looper.getMainLooper()) // The handler responsible for handling posting [axisRunnable] val axisHandler = Handler(Looper.getMainLooper()) // The handler responsible for handling posting [axisRunnable]
// The last values of the HAT axes so that they can be ignored in [View.OnGenericMotionListener] so they are passed onto [DialogInterface.OnKeyListener] as [KeyEvent]s
var oldDpadX = 0.0f
var oldDpadY = 0.0f
dialog?.setOnKeyListener { _, _, event -> dialog?.setOnKeyListener { _, _, event ->
// We want all input events from Joysticks and Buttons except for [KeyEvent.KEYCODE_BACK] as that will should be processed elsewhere // We want all input events from Joysticks and Buttons except for [KeyEvent.KEYCODE_BACK] as that will should be processed elsewhere
if (((event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON) && event.keyCode != KeyEvent.KEYCODE_BACK) || event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) && event.repeatCount == 0) { if (((event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON) && event.keyCode != KeyEvent.KEYCODE_BACK) || event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK)) && event.repeatCount == 0) {
@ -155,23 +151,18 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
} }
} }
val axes = arrayOf(MotionEvent.AXIS_X, MotionEvent.AXIS_Y, MotionEvent.AXIS_Z, MotionEvent.AXIS_RZ, MotionEvent.AXIS_HAT_X, MotionEvent.AXIS_HAT_Y, MotionEvent.AXIS_LTRIGGER, MotionEvent.AXIS_RTRIGGER, MotionEvent.AXIS_THROTTLE, MotionEvent.AXIS_RUDDER, MotionEvent.AXIS_WHEEL, MotionEvent.AXIS_GAS, MotionEvent.AXIS_BRAKE).plus(IntRange(MotionEvent.AXIS_GENERIC_1, MotionEvent.AXIS_GENERIC_16).toList()) val axes = MotionHostEvent.axes
val axesHistory = arrayOfNulls<Float>(axes.size) // The last recorded value of an axis, this is used to eliminate any stagnant axes val axesHistory = arrayOfNulls<Float>(axes.size) // The last recorded value of an axis, this is used to eliminate any stagnant axes
view.setOnGenericMotionListener { _, event -> view.setOnGenericMotionListener { _, event ->
// We retrieve the value of the HAT axes so that we can check for change and ignore any input from them so it'll be passed onto the [KeyEvent] handler
val dpadX = event.getAxisValue(MotionEvent.AXIS_HAT_X)
val dpadY = event.getAxisValue(MotionEvent.AXIS_HAT_Y)
// We want all input events from Joysticks and Buttons that are [MotionEvent.ACTION_MOVE] and not from the D-pad // We want all input events from Joysticks and Buttons that are [MotionEvent.ACTION_MOVE] and not from the D-pad
if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE && dpadX == oldDpadX && dpadY == oldDpadY) { if ((event.isFromSource(InputDevice.SOURCE_CLASS_JOYSTICK) || event.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) && event.action == MotionEvent.ACTION_MOVE) {
// We iterate over every axis to check if any of them pass the selection threshold and if they do then select them by setting [deviceId], [inputId] and [axisPolarity] // We iterate over every axis to check if any of them pass the selection threshold and if they do then select them by setting [deviceId], [inputId] and [axisPolarity]
for (axisItem in axes.withIndex()) { for (axisItem in axes.withIndex()) {
val axis = axisItem.value val axis = axisItem.value
val value = event.getAxisValue(axis) val value = event.getAxisValue(axis)
// This checks the history of the axis so it we can ignore any stagnant axis // This checks the history of the axis so we can ignore any stagnant axis
if ((event.historySize == 0 || value == event.getHistoricalAxisValue(axis, 0)) && (axesHistory[axisItem.index]?.let { it == value } != false)) { if ((event.historySize == 0 || value == event.getHistoricalAxisValue(axis, 0)) && (axesHistory[axisItem.index]?.let { it == value } != false)) {
axesHistory[axisItem.index] = value axesHistory[axisItem.index] = value
continue continue
@ -246,9 +237,6 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
true true
} else { } else {
oldDpadX = dpadX
oldDpadY = dpadY
false false
} }
} }