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_Z,
MotionEvent.AXIS_RZ,
MotionEvent.AXIS_HAT_X,
MotionEvent.AXIS_HAT_Y,
MotionEvent.AXIS_LTRIGGER,
MotionEvent.AXIS_RTRIGGER,
MotionEvent.AXIS_THROTTLE,

View File

@ -5,7 +5,6 @@
package emu.skyline.input
import android.graphics.PointF
import android.view.InputDevice
import android.view.KeyEvent
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)
/**
* 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
*/
fun handleMotionEvent(event : MotionEvent) : Boolean {
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) {
for (axisItem in MotionHostEvent.axes.withIndex()) {
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)) {
var polarity = value > 0 || (value == 0f && axesHistory[axisItem.index]?.let { it >= 0 } == true)
if ((event.historySize != 0 && value != event.getHistoricalAxisValue(axis, 0)) || (axesHistory[axisItem.index]?.let { it == value } == false)) {
var polarity = value > 0 || (value == 0f && axesHistory[axisItem.index]?.let { it >= 0 } == true)
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
inputManager.eventMap[hostEvent] ?: if (value == 0f) {
polarity = false
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())
}
val guestEvent = MotionHostEvent(event.device.descriptor, axis, polarity).let { hostEvent ->
inputManager.eventMap[hostEvent] ?: if (value == 0f) {
polarity = false
inputManager.eventMap[hostEvent.copy(polarity = false)]
} else {
null
}
}
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
} else {
oldHat = hat
axesHistory[axisItem.index] = value
}
return true
}
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
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 ->
// 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) {
@ -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
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
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]
for (axisItem in axes.withIndex()) {
val axis = axisItem.value
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)) {
axesHistory[axisItem.index] = value
continue
@ -246,9 +237,6 @@ class ButtonDialog @JvmOverloads constructor(private val item : ControllerButton
true
} else {
oldDpadX = dpadX
oldDpadY = dpadY
false
}
}