Merge pull request #11925 from t895/controller-fix
android: Fix controllers stuck on player 2
This commit is contained in:
commit
ab3e3c11af
3 changed files with 54 additions and 87 deletions
|
@ -45,7 +45,6 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||||
import org.yuzu.yuzu_emu.model.Game
|
import org.yuzu.yuzu_emu.model.Game
|
||||||
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
|
|
||||||
import org.yuzu.yuzu_emu.utils.ForegroundService
|
import org.yuzu.yuzu_emu.utils.ForegroundService
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
import org.yuzu.yuzu_emu.utils.MemoryUtil
|
||||||
|
@ -57,17 +56,16 @@ import kotlin.math.roundToInt
|
||||||
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
private lateinit var binding: ActivityEmulationBinding
|
private lateinit var binding: ActivityEmulationBinding
|
||||||
|
|
||||||
private var controllerMappingHelper: ControllerMappingHelper? = null
|
|
||||||
|
|
||||||
var isActivityRecreated = false
|
var isActivityRecreated = false
|
||||||
private lateinit var nfcReader: NfcReader
|
private lateinit var nfcReader: NfcReader
|
||||||
private lateinit var inputHandler: InputHandler
|
|
||||||
|
|
||||||
private val gyro = FloatArray(3)
|
private val gyro = FloatArray(3)
|
||||||
private val accel = FloatArray(3)
|
private val accel = FloatArray(3)
|
||||||
private var motionTimestamp: Long = 0
|
private var motionTimestamp: Long = 0
|
||||||
private var flipMotionOrientation: Boolean = false
|
private var flipMotionOrientation: Boolean = false
|
||||||
|
|
||||||
|
private var controllerIds = InputHandler.getGameControllerIds()
|
||||||
|
|
||||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||||
private val actionMute = "ACTION_EMULATOR_MUTE"
|
private val actionMute = "ACTION_EMULATOR_MUTE"
|
||||||
|
@ -95,8 +93,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
|
|
||||||
isActivityRecreated = savedInstanceState != null
|
isActivityRecreated = savedInstanceState != null
|
||||||
|
|
||||||
controllerMappingHelper = ControllerMappingHelper()
|
|
||||||
|
|
||||||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||||
enableFullscreenImmersive()
|
enableFullscreenImmersive()
|
||||||
|
|
||||||
|
@ -105,8 +101,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
nfcReader = NfcReader(this)
|
nfcReader = NfcReader(this)
|
||||||
nfcReader.initialize()
|
nfcReader.initialize()
|
||||||
|
|
||||||
inputHandler = InputHandler()
|
InputHandler.initialize()
|
||||||
inputHandler.initialize()
|
|
||||||
|
|
||||||
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||||
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
|
||||||
|
@ -162,6 +157,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
nfcReader.startScanning()
|
nfcReader.startScanning()
|
||||||
startMotionSensorListener()
|
startMotionSensorListener()
|
||||||
|
InputHandler.updateControllerIds()
|
||||||
|
|
||||||
buildPictureInPictureParams()
|
buildPictureInPictureParams()
|
||||||
}
|
}
|
||||||
|
@ -195,7 +191,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputHandler.dispatchKeyEvent(event)
|
return InputHandler.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||||
|
@ -210,7 +206,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputHandler.dispatchGenericMotionEvent(event)
|
return InputHandler.dispatchGenericMotionEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSensorChanged(event: SensorEvent) {
|
override fun onSensorChanged(event: SensorEvent) {
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
|
||||||
|
|
||||||
import android.view.InputDevice
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Some controllers have incorrect mappings. This class has special-case fixes for them.
|
|
||||||
*/
|
|
||||||
class ControllerMappingHelper {
|
|
||||||
/**
|
|
||||||
* Some controllers report extra button presses that can be ignored.
|
|
||||||
*/
|
|
||||||
fun shouldKeyBeIgnored(inputDevice: InputDevice, keyCode: Int): Boolean {
|
|
||||||
return if (isDualShock4(inputDevice)) {
|
|
||||||
// The two analog triggers generate analog motion events as well as a keycode.
|
|
||||||
// We always prefer to use the analog values, so throw away the button press
|
|
||||||
keyCode == KeyEvent.KEYCODE_BUTTON_L2 || keyCode == KeyEvent.KEYCODE_BUTTON_R2
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scale an axis to be zero-centered with a proper range.
|
|
||||||
*/
|
|
||||||
fun scaleAxis(inputDevice: InputDevice, axis: Int, value: Float): Float {
|
|
||||||
if (isDualShock4(inputDevice)) {
|
|
||||||
// Android doesn't have correct mappings for this controller's triggers. It reports them
|
|
||||||
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
|
|
||||||
// Scale them to properly zero-centered with a range of [0.0, 1.0].
|
|
||||||
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
|
|
||||||
return (value + 1) / 2.0f
|
|
||||||
}
|
|
||||||
} else if (isXboxOneWireless(inputDevice)) {
|
|
||||||
// Same as the DualShock 4, the mappings are missing.
|
|
||||||
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
|
|
||||||
return (value + 1) / 2.0f
|
|
||||||
}
|
|
||||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
|
||||||
// This axis is stuck at ~.5. Ignore it.
|
|
||||||
return 0.0f
|
|
||||||
}
|
|
||||||
} else if (isMogaPro2Hid(inputDevice)) {
|
|
||||||
// This controller has a broken axis that reports a constant value. Ignore it.
|
|
||||||
if (axis == MotionEvent.AXIS_GENERIC_1) {
|
|
||||||
return 0.0f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sony DualShock 4 controller
|
|
||||||
private fun isDualShock4(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x54c && inputDevice.productId == 0x9cc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft Xbox One controller
|
|
||||||
private fun isXboxOneWireless(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x45e && inputDevice.productId == 0x2e0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Moga Pro 2 HID
|
|
||||||
private fun isMogaPro2Hid(inputDevice: InputDevice): Boolean {
|
|
||||||
return inputDevice.vendorId == 0x20d6 && inputDevice.productId == 0x6271
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,17 +3,24 @@
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.utils
|
package org.yuzu.yuzu_emu.utils
|
||||||
|
|
||||||
|
import android.view.InputDevice
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
|
|
||||||
class InputHandler {
|
object InputHandler {
|
||||||
|
private var controllerIds = getGameControllerIds()
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
// Connect first controller
|
// Connect first controller
|
||||||
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
|
NativeLibrary.onGamePadConnectEvent(getPlayerNumber(NativeLibrary.Player1Device))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateControllerIds() {
|
||||||
|
controllerIds = getGameControllerIds()
|
||||||
|
}
|
||||||
|
|
||||||
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
val button: Int = when (event.device.vendorId) {
|
val button: Int = when (event.device.vendorId) {
|
||||||
0x045E -> getInputXboxButtonKey(event.keyCode)
|
0x045E -> getInputXboxButtonKey(event.keyCode)
|
||||||
|
@ -35,7 +42,7 @@ class InputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
return NativeLibrary.onGamePadButtonEvent(
|
return NativeLibrary.onGamePadButtonEvent(
|
||||||
getPlayerNumber(event.device.controllerNumber),
|
getPlayerNumber(event.device.controllerNumber, event.deviceId),
|
||||||
button,
|
button,
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
|
@ -58,9 +65,14 @@ class InputHandler {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPlayerNumber(index: Int): Int {
|
private fun getPlayerNumber(index: Int, deviceId: Int = -1): Int {
|
||||||
|
var deviceIndex = index
|
||||||
|
if (deviceId != -1) {
|
||||||
|
deviceIndex = controllerIds[deviceId]!!
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Joycons are handled as different controllers. Find a way to merge them.
|
// TODO: Joycons are handled as different controllers. Find a way to merge them.
|
||||||
return when (index) {
|
return when (deviceIndex) {
|
||||||
2 -> NativeLibrary.Player2Device
|
2 -> NativeLibrary.Player2Device
|
||||||
3 -> NativeLibrary.Player3Device
|
3 -> NativeLibrary.Player3Device
|
||||||
4 -> NativeLibrary.Player4Device
|
4 -> NativeLibrary.Player4Device
|
||||||
|
@ -238,7 +250,7 @@ class InputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
|
private fun setGenericAxisInput(event: MotionEvent, axis: Int) {
|
||||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
when (axis) {
|
when (axis) {
|
||||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
@ -297,7 +309,7 @@ class InputHandler {
|
||||||
|
|
||||||
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
private fun setJoyconAxisInput(event: MotionEvent, axis: Int) {
|
||||||
// Joycon support is half dead. Right joystick doesn't work
|
// Joycon support is half dead. Right joystick doesn't work
|
||||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
when (axis) {
|
when (axis) {
|
||||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
@ -325,7 +337,7 @@ class InputHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
|
private fun setRazerAxisInput(event: MotionEvent, axis: Int) {
|
||||||
val playerNumber = getPlayerNumber(event.device.controllerNumber)
|
val playerNumber = getPlayerNumber(event.device.controllerNumber, event.deviceId)
|
||||||
|
|
||||||
when (axis) {
|
when (axis) {
|
||||||
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
MotionEvent.AXIS_X, MotionEvent.AXIS_Y ->
|
||||||
|
@ -362,4 +374,33 @@ class InputHandler {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getGameControllerIds(): Map<Int, Int> {
|
||||||
|
val gameControllerDeviceIds = mutableMapOf<Int, Int>()
|
||||||
|
val deviceIds = InputDevice.getDeviceIds()
|
||||||
|
var controllerSlot = 1
|
||||||
|
deviceIds.forEach { deviceId ->
|
||||||
|
InputDevice.getDevice(deviceId)?.apply {
|
||||||
|
// Don't over-assign controllers
|
||||||
|
if (controllerSlot >= 8) {
|
||||||
|
return gameControllerDeviceIds
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
) {
|
||||||
|
// This device is a game controller. Store its device ID.
|
||||||
|
if (deviceId and id and vendorId and productId != 0) {
|
||||||
|
// Additionally filter out devices that have no ID
|
||||||
|
gameControllerDeviceIds
|
||||||
|
.takeIf { !it.contains(deviceId) }
|
||||||
|
?.put(deviceId, controllerSlot)
|
||||||
|
controllerSlot++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gameControllerDeviceIds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue